~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/repository.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2008-06-05 04:05:05 UTC
  • mfrom: (3473.1.1 ianc-integration)
  • Revision ID: pqm@pqm.ubuntu.com-20080605040505-i9kqxg2fps2qjdi0
Add the 'alias' command (Tim Penhey)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006, 2007, 2008 Canonical Ltd
 
1
# Copyright (C) 2005, 2006, 2007 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
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
16
 
 
17
from cStringIO import StringIO
 
18
 
17
19
from bzrlib.lazy_import import lazy_import
18
20
lazy_import(globals(), """
19
 
import cStringIO
20
21
import re
21
22
import time
22
23
 
33
34
    lockdir,
34
35
    lru_cache,
35
36
    osutils,
 
37
    registry,
36
38
    remote,
37
39
    revision as _mod_revision,
38
40
    symbol_versioning,
 
41
    transactions,
39
42
    tsort,
40
43
    ui,
41
44
    )
42
45
from bzrlib.bundle import serializer
43
46
from bzrlib.revisiontree import RevisionTree
44
47
from bzrlib.store.versioned import VersionedFileStore
 
48
from bzrlib.store.text import TextStore
45
49
from bzrlib.testament import Testament
 
50
from bzrlib.util import bencode
46
51
""")
47
52
 
48
 
from bzrlib import registry
49
53
from bzrlib.decorators import needs_read_lock, needs_write_lock
50
54
from bzrlib.inter import InterObject
51
55
from bzrlib.inventory import Inventory, InventoryDirectory, ROOT_ID
52
56
from bzrlib.symbol_versioning import (
53
57
        deprecated_method,
54
 
        one_one,
55
 
        one_two,
56
 
        one_six,
57
58
        )
58
 
from bzrlib.trace import mutter, mutter_callsite, warning
 
59
from bzrlib.trace import mutter, mutter_callsite, note, warning
59
60
 
60
61
 
61
62
# Old formats display a warning, but only once
157
158
        """Tell the builder that the inventory is finished."""
158
159
        if self.new_inventory.root is None:
159
160
            raise AssertionError('Root entry should be supplied to'
160
 
                ' record_entry_contents, as of bzr 0.10.')
 
161
                ' record_entry_contents, as of bzr 0.10.',
 
162
                 DeprecationWarning, stacklevel=2)
161
163
            self.new_inventory.add(InventoryDirectory(ROOT_ID, '', None))
162
164
        self.new_inventory.revision_id = self._new_revision_id
163
165
        self.inv_sha1 = self.repository.add_inventory(
236
238
            content - stat, length, exec, sha/link target. This is only
237
239
            accessed when the entry has a revision of None - that is when it is
238
240
            a candidate to commit.
239
 
        :return: A tuple (change_delta, version_recorded, fs_hash).
240
 
            change_delta is an inventory_delta change for this entry against
241
 
            the basis tree of the commit, or None if no change occured against
242
 
            the basis tree.
 
241
        :return: A tuple (change_delta, version_recorded). change_delta is 
 
242
            an inventory_delta change for this entry against the basis tree of
 
243
            the commit, or None if no change occured against the basis tree.
243
244
            version_recorded is True if a new version of the entry has been
244
245
            recorded. For instance, committing a merge where a file was only
245
246
            changed on the other side will return (delta, False).
246
 
            fs_hash is either None, or the hash details for the path (currently
247
 
            a tuple of the contents sha1 and the statvalue returned by
248
 
            tree.get_file_with_stat()).
249
247
        """
250
248
        if self.new_inventory.root is None:
251
249
            if ie.parent_id is not None:
286
284
                else:
287
285
                    # add
288
286
                    delta = (None, path, ie.file_id, ie)
289
 
                return delta, False, None
 
287
                return delta, False
290
288
            else:
291
289
                # we don't need to commit this, because the caller already
292
290
                # determined that an existing revision of this file is
293
 
                # appropriate. If its not being considered for committing then
294
 
                # it and all its parents to the root must be unaltered so
295
 
                # no-change against the basis.
296
 
                if ie.revision == self._new_revision_id:
297
 
                    raise AssertionError("Impossible situation, a skipped "
298
 
                        "inventory entry (%r) claims to be modified in this "
299
 
                        "commit (%r).", (ie, self._new_revision_id))
300
 
                return None, False, None
 
291
                # appropriate.
 
292
                return None, (ie.revision == self._new_revision_id)
301
293
        # XXX: Friction: parent_candidates should return a list not a dict
302
294
        #      so that we don't have to walk the inventories again.
303
295
        parent_candiate_entries = ie.parent_candidates(parent_invs)
333
325
            # if the kind changed the content obviously has
334
326
            if kind != parent_entry.kind:
335
327
                store = True
336
 
        # Stat cache fingerprint feedback for the caller - None as we usually
337
 
        # don't generate one.
338
 
        fingerprint = None
339
328
        if kind == 'file':
340
329
            if content_summary[2] is None:
341
330
                raise ValueError("Files must not have executable = None")
352
341
                    ie.text_size = parent_entry.text_size
353
342
                    ie.text_sha1 = parent_entry.text_sha1
354
343
                    ie.executable = parent_entry.executable
355
 
                    return self._get_delta(ie, basis_inv, path), False, None
 
344
                    return self._get_delta(ie, basis_inv, path), False
356
345
                else:
357
346
                    # Either there is only a hash change(no hash cache entry,
358
347
                    # or same size content change), or there is no change on
365
354
                # absence of a content change in the file.
366
355
                nostore_sha = None
367
356
            ie.executable = content_summary[2]
368
 
            file_obj, stat_value = tree.get_file_with_stat(ie.file_id, path)
369
 
            try:
370
 
                lines = file_obj.readlines()
371
 
            finally:
372
 
                file_obj.close()
 
357
            lines = tree.get_file(ie.file_id, path).readlines()
373
358
            try:
374
359
                ie.text_sha1, ie.text_size = self._add_text_to_weave(
375
360
                    ie.file_id, lines, heads, nostore_sha)
376
 
                # Let the caller know we generated a stat fingerprint.
377
 
                fingerprint = (ie.text_sha1, stat_value)
378
361
            except errors.ExistingContent:
379
362
                # Turns out that the file content was unchanged, and we were
380
363
                # only going to store a new node if it was changed. Carry over
383
366
                ie.text_size = parent_entry.text_size
384
367
                ie.text_sha1 = parent_entry.text_sha1
385
368
                ie.executable = parent_entry.executable
386
 
                return self._get_delta(ie, basis_inv, path), False, None
 
369
                return self._get_delta(ie, basis_inv, path), False
387
370
        elif kind == 'directory':
388
371
            if not store:
389
372
                # all data is meta here, nothing specific to directory, so
390
373
                # carry over:
391
374
                ie.revision = parent_entry.revision
392
 
                return self._get_delta(ie, basis_inv, path), False, None
 
375
                return self._get_delta(ie, basis_inv, path), False
393
376
            lines = []
394
377
            self._add_text_to_weave(ie.file_id, lines, heads, None)
395
378
        elif kind == 'symlink':
403
386
                # unchanged, carry over.
404
387
                ie.revision = parent_entry.revision
405
388
                ie.symlink_target = parent_entry.symlink_target
406
 
                return self._get_delta(ie, basis_inv, path), False, None
 
389
                return self._get_delta(ie, basis_inv, path), False
407
390
            ie.symlink_target = current_link_target
408
391
            lines = []
409
392
            self._add_text_to_weave(ie.file_id, lines, heads, None)
415
398
                # unchanged, carry over.
416
399
                ie.reference_revision = parent_entry.reference_revision
417
400
                ie.revision = parent_entry.revision
418
 
                return self._get_delta(ie, basis_inv, path), False, None
 
401
                return self._get_delta(ie, basis_inv, path), False
419
402
            ie.reference_revision = content_summary[3]
420
403
            lines = []
421
404
            self._add_text_to_weave(ie.file_id, lines, heads, None)
422
405
        else:
423
406
            raise NotImplementedError('unknown kind')
424
407
        ie.revision = self._new_revision_id
425
 
        return self._get_delta(ie, basis_inv, path), True, fingerprint
 
408
        return self._get_delta(ie, basis_inv, path), True
426
409
 
427
410
    def _add_text_to_weave(self, file_id, new_lines, parents, nostore_sha):
 
411
        versionedfile = self.repository.weave_store.get_weave_or_empty(
 
412
            file_id, self.repository.get_transaction())
 
413
        # Don't change this to add_lines - add_lines_with_ghosts is cheaper
 
414
        # than add_lines, and allows committing when a parent is ghosted for
 
415
        # some reason.
428
416
        # Note: as we read the content directly from the tree, we know its not
429
417
        # been turned into unicode or badly split - but a broken tree
430
418
        # implementation could give us bad output from readlines() so this is
431
419
        # not a guarantee of safety. What would be better is always checking
432
420
        # the content during test suite execution. RBC 20070912
433
 
        parent_keys = tuple((file_id, parent) for parent in parents)
434
 
        return self.repository.texts.add_lines(
435
 
            (file_id, self._new_revision_id), parent_keys, new_lines,
 
421
        return versionedfile.add_lines_with_ghosts(
 
422
            self._new_revision_id, parents, new_lines,
436
423
            nostore_sha=nostore_sha, random_id=self.random_revid,
437
424
            check_content=False)[0:2]
438
425
 
463
450
    revisions and file history.  It's normally accessed only by the Branch,
464
451
    which views a particular line of development through that history.
465
452
 
466
 
    The Repository builds on top of some byte storage facilies (the revisions,
467
 
    signatures, inventories and texts attributes) and a Transport, which
468
 
    respectively provide byte storage and a means to access the (possibly
 
453
    The Repository builds on top of Stores and a Transport, which respectively 
 
454
    describe the disk data format and the way of accessing the (possibly 
469
455
    remote) disk.
470
456
 
471
 
    The byte storage facilities are addressed via tuples, which we refer to
472
 
    as 'keys' throughout the code base. Revision_keys, inventory_keys and
473
 
    signature_keys are all 1-tuples: (revision_id,). text_keys are two-tuples:
474
 
    (file_id, revision_id). We use this interface because it allows low
475
 
    friction with the underlying code that implements disk indices, network
476
 
    encoding and other parts of bzrlib.
477
 
 
478
 
    :ivar revisions: A bzrlib.versionedfile.VersionedFiles instance containing
479
 
        the serialised revisions for the repository. This can be used to obtain
480
 
        revision graph information or to access raw serialised revisions.
481
 
        The result of trying to insert data into the repository via this store
482
 
        is undefined: it should be considered read-only except for implementors
483
 
        of repositories.
484
 
    :ivar signatures: A bzrlib.versionedfile.VersionedFiles instance containing
485
 
        the serialised signatures for the repository. This can be used to
486
 
        obtain access to raw serialised signatures.  The result of trying to
487
 
        insert data into the repository via this store is undefined: it should
488
 
        be considered read-only except for implementors of repositories.
489
 
    :ivar inventories: A bzrlib.versionedfile.VersionedFiles instance containing
490
 
        the serialised inventories for the repository. This can be used to
491
 
        obtain unserialised inventories.  The result of trying to insert data
492
 
        into the repository via this store is undefined: it should be
493
 
        considered read-only except for implementors of repositories.
494
 
    :ivar texts: A bzrlib.versionedfile.VersionedFiles instance containing the
495
 
        texts of files and directories for the repository. This can be used to
496
 
        obtain file texts or file graphs. Note that Repository.iter_file_bytes
497
 
        is usually a better interface for accessing file texts.
498
 
        The result of trying to insert data into the repository via this store
499
 
        is undefined: it should be considered read-only except for implementors
500
 
        of repositories.
501
457
    :ivar _transport: Transport for file access to repository, typically
502
458
        pointing to .bzr/repository.
503
459
    """
537
493
        attempted.
538
494
        """
539
495
 
540
 
    def add_fallback_repository(self, repository):
541
 
        """Add a repository to use for looking up data not held locally.
542
 
        
543
 
        :param repository: A repository.
544
 
        """
545
 
        if not self._format.supports_external_lookups:
546
 
            raise errors.UnstackableRepositoryFormat(self._format, self.base)
547
 
        self._check_fallback_repository(repository)
548
 
        self._fallback_repositories.append(repository)
549
 
        self.texts.add_fallback_versioned_files(repository.texts)
550
 
        self.inventories.add_fallback_versioned_files(repository.inventories)
551
 
        self.revisions.add_fallback_versioned_files(repository.revisions)
552
 
        self.signatures.add_fallback_versioned_files(repository.signatures)
553
 
 
554
 
    def _check_fallback_repository(self, repository):
555
 
        """Check that this repository can fallback to repository safely.
556
 
 
557
 
        Raise an error if not.
558
 
        
559
 
        :param repository: A repository to fallback to.
560
 
        """
561
 
        return InterRepository._assert_same_model(self, repository)
562
 
 
563
496
    def add_inventory(self, revision_id, inv, parents):
564
497
        """Add the inventory inv to the repository as revision_id.
565
498
        
580
513
        if inv.root is None:
581
514
            raise AssertionError()
582
515
        inv_lines = self._serialise_inventory_to_lines(inv)
583
 
        return self._inventory_add_lines(revision_id, parents,
 
516
        inv_vf = self.get_inventory_weave()
 
517
        return self._inventory_add_lines(inv_vf, revision_id, parents,
584
518
            inv_lines, check_content=False)
585
519
 
586
 
    def _inventory_add_lines(self, revision_id, parents, lines,
 
520
    def _inventory_add_lines(self, inv_vf, revision_id, parents, lines,
587
521
        check_content=True):
588
522
        """Store lines in inv_vf and return the sha1 of the inventory."""
589
 
        parents = [(parent,) for parent in parents]
590
 
        return self.inventories.add_lines((revision_id,), parents, lines,
 
523
        final_parents = []
 
524
        for parent in parents:
 
525
            if parent in inv_vf:
 
526
                final_parents.append(parent)
 
527
        return inv_vf.add_lines(revision_id, final_parents, lines,
591
528
            check_content=check_content)[0]
592
529
 
593
530
    def add_revision(self, revision_id, rev, inv=None, config=None):
610
547
            plaintext = Testament(rev, inv).as_short_text()
611
548
            self.store_revision_signature(
612
549
                gpg.GPGStrategy(config), plaintext, revision_id)
613
 
        # check inventory present
614
 
        if not self.inventories.get_parent_map([(revision_id,)]):
 
550
        inventory_vf = self.get_inventory_weave()
 
551
        if not revision_id in inventory_vf:
615
552
            if inv is None:
616
553
                raise errors.WeaveRevisionNotPresent(revision_id,
617
 
                                                     self.inventories)
 
554
                                                     inventory_vf)
618
555
            else:
619
556
                # yes, this is not suitable for adding with ghosts.
620
557
                rev.inventory_sha1 = self.add_inventory(revision_id, inv,
621
558
                                                        rev.parent_ids)
622
559
        else:
623
 
            key = (revision_id,)
624
 
            rev.inventory_sha1 = self.inventories.get_sha1s([key])[key]
625
 
        self._add_revision(rev)
 
560
            rev.inventory_sha1 = inventory_vf.get_sha1s([revision_id])[0]
 
561
        self._revision_store.add_revision(rev, self.get_transaction())
626
562
 
627
 
    def _add_revision(self, revision):
628
 
        text = self._serializer.write_revision_to_string(revision)
629
 
        key = (revision.revision_id,)
630
 
        parents = tuple((parent,) for parent in revision.parent_ids)
631
 
        self.revisions.add_lines(key, parents, osutils.split_lines(text))
 
563
    def _add_revision_text(self, revision_id, text):
 
564
        revision = self._revision_store._serializer.read_revision_from_string(
 
565
            text)
 
566
        self._revision_store._add_revision(revision, StringIO(text),
 
567
                                           self.get_transaction())
632
568
 
633
569
    def all_revision_ids(self):
634
570
        """Returns a list of all the revision ids in the repository. 
635
571
 
636
 
        This is conceptually deprecated because code should generally work on
637
 
        the graph reachable from a particular revision, and ignore any other
638
 
        revisions that might be present.  There is no direct replacement
639
 
        method.
 
572
        This is deprecated because code should generally work on the graph
 
573
        reachable from a particular revision, and ignore any other revisions
 
574
        that might be present.  There is no direct replacement method.
640
575
        """
641
576
        if 'evil' in debug.debug_flags:
642
577
            mutter_callsite(2, "all_revision_ids is linear with history.")
675
610
        """Construct the current default format repository in a_bzrdir."""
676
611
        return RepositoryFormat.get_default_format().initialize(a_bzrdir)
677
612
 
678
 
    def __init__(self, _format, a_bzrdir, control_files):
 
613
    def __init__(self, _format, a_bzrdir, control_files,
 
614
                 _revision_store, control_store, text_store):
679
615
        """instantiate a Repository.
680
616
 
681
617
        :param _format: The format of the repository on disk.
692
628
        self.control_files = control_files
693
629
        self._transport = control_files._transport
694
630
        self.base = self._transport.base
 
631
        self._revision_store = _revision_store
 
632
        # backwards compatibility
 
633
        self.weave_store = text_store
695
634
        # for tests
696
635
        self._reconcile_does_inventory_gc = True
697
636
        self._reconcile_fixes_text_parents = False
698
637
        self._reconcile_backsup_inventory = True
699
638
        # not right yet - should be more semantically clear ? 
700
639
        # 
 
640
        self.control_store = control_store
 
641
        self.control_weaves = control_store
701
642
        # TODO: make sure to construct the right store classes, etc, depending
702
643
        # on whether escaping is required.
703
644
        self._warn_if_deprecated()
704
645
        self._write_group = None
705
 
        # Additional places to query for data.
706
 
        self._fallback_repositories = []
707
 
        # What order should fetch operations request streams in?
708
 
        # The default is unordered as that is the cheapest for an origin to
709
 
        # provide.
710
 
        self._fetch_order = 'unordered'
711
 
        # Does this repository use deltas that can be fetched as-deltas ?
712
 
        # (E.g. knits, where the knit deltas can be transplanted intact.
713
 
        # We default to False, which will ensure that enough data to get
714
 
        # a full text out of any fetch stream will be grabbed.
715
 
        self._fetch_uses_deltas = False
716
 
        # Should fetch trigger a reconcile after the fetch? Only needed for
717
 
        # some repository formats that can suffer internal inconsistencies.
718
 
        self._fetch_reconcile = False
719
646
 
720
647
    def __repr__(self):
721
648
        return '%s(%r)' % (self.__class__.__name__,
769
696
        XXX: this docstring is duplicated in many places, e.g. lockable_files.py
770
697
        """
771
698
        result = self.control_files.lock_write(token=token)
772
 
        for repo in self._fallback_repositories:
773
 
            # Writes don't affect fallback repos
774
 
            repo.lock_read()
775
699
        self._refresh_data()
776
700
        return result
777
701
 
778
702
    def lock_read(self):
779
703
        self.control_files.lock_read()
780
 
        for repo in self._fallback_repositories:
781
 
            repo.lock_read()
782
704
        self._refresh_data()
783
705
 
784
706
    def get_physical_lock_status(self):
844
766
                last_revision.timezone)
845
767
 
846
768
        # now gather global repository information
847
 
        # XXX: This is available for many repos regardless of listability.
848
769
        if self.bzrdir.root_transport.listable():
849
 
            # XXX: do we want to __define len__() ?
850
 
            # Maybe the versionedfiles object should provide a different
851
 
            # method to get the number of keys.
852
 
            result['revisions'] = len(self.revisions.keys())
853
 
            # result['size'] = t
 
770
            c, t = self._revision_store.total_size(self.get_transaction())
 
771
            result['revisions'] = c
 
772
            result['size'] = t
854
773
        return result
855
774
 
856
775
    def find_branches(self, using=False):
896
815
                branches.extend(repository.find_branches())
897
816
        return branches
898
817
 
 
818
    def get_data_stream(self, revision_ids):
 
819
        raise NotImplementedError(self.get_data_stream)
 
820
 
 
821
    def get_data_stream_for_search(self, search_result):
 
822
        """Get a data stream that can be inserted to a repository.
 
823
 
 
824
        :param search_result: A bzrlib.graph.SearchResult selecting the
 
825
            revisions to get.
 
826
        :return: A data stream that can be inserted into a repository using
 
827
            insert_data_stream.
 
828
        """
 
829
        raise NotImplementedError(self.get_data_stream_for_search)
 
830
 
 
831
    def insert_data_stream(self, stream):
 
832
        """XXX What does this really do? 
 
833
        
 
834
        Is it a substitute for fetch? 
 
835
        Should it manage its own write group ?
 
836
        """
 
837
        for item_key, bytes in stream:
 
838
            if item_key[0] == 'file':
 
839
                (file_id,) = item_key[1:]
 
840
                knit = self.weave_store.get_weave_or_empty(
 
841
                    file_id, self.get_transaction())
 
842
            elif item_key == ('inventory',):
 
843
                knit = self.get_inventory_weave()
 
844
            elif item_key == ('revisions',):
 
845
                knit = self._revision_store.get_revision_file(
 
846
                    self.get_transaction())
 
847
            elif item_key == ('signatures',):
 
848
                knit = self._revision_store.get_signature_file(
 
849
                    self.get_transaction())
 
850
            else:
 
851
                raise errors.RepositoryDataStreamError(
 
852
                    "Unrecognised data stream key '%s'" % (item_key,))
 
853
            decoded_list = bencode.bdecode(bytes)
 
854
            format = decoded_list.pop(0)
 
855
            data_list = []
 
856
            knit_bytes = ''
 
857
            for version, options, parents, some_bytes in decoded_list:
 
858
                data_list.append((version, options, len(some_bytes), parents))
 
859
                knit_bytes += some_bytes
 
860
            buffer = StringIO(knit_bytes)
 
861
            def reader_func(count):
 
862
                if count is None:
 
863
                    return buffer.read()
 
864
                else:
 
865
                    return buffer.read(count)
 
866
            knit.insert_data_stream(
 
867
                (format, data_list, reader_func))
 
868
 
899
869
    @needs_read_lock
900
870
    def search_missing_revision_ids(self, other, revision_id=None, find_ghosts=True):
901
871
        """Return the revision ids that other has that this does not.
907
877
        return InterRepository.get(other, self).search_missing_revision_ids(
908
878
            revision_id, find_ghosts)
909
879
 
910
 
    @deprecated_method(one_two)
 
880
    @deprecated_method(symbol_versioning.one_two)
911
881
    @needs_read_lock
912
882
    def missing_revision_ids(self, other, revision_id=None, find_ghosts=True):
913
883
        """Return the revision ids that other has that this does not.
981
951
                not _mod_revision.is_null(revision_id)):
982
952
                self.get_revision(revision_id)
983
953
            return 0, []
984
 
        # if there is no specific appropriate InterRepository, this will get
985
 
        # the InterRepository base class, which raises an
986
 
        # IncompatibleRepositories when asked to fetch.
987
954
        inter = InterRepository.get(source, self)
988
 
        return inter.fetch(revision_id=revision_id, pb=pb,
989
 
            find_ghosts=find_ghosts)
 
955
        try:
 
956
            return inter.fetch(revision_id=revision_id, pb=pb, find_ghosts=find_ghosts)
 
957
        except NotImplementedError:
 
958
            raise errors.IncompatibleRepositories(source, self)
990
959
 
991
960
    def create_bundle(self, target, base, fileobj, format=None):
992
961
        return serializer.write_bundle(self, target, base, fileobj, format)
1019
988
                raise errors.BzrError(
1020
989
                    'Must end write groups before releasing write locks.')
1021
990
        self.control_files.unlock()
1022
 
        for repo in self._fallback_repositories:
1023
 
            repo.unlock()
1024
991
 
1025
992
    @needs_read_lock
1026
993
    def clone(self, a_bzrdir, revision_id=None):
1094
1061
        """True if this repository has a copy of the revision."""
1095
1062
        return revision_id in self.has_revisions((revision_id,))
1096
1063
 
1097
 
    @needs_read_lock
1098
1064
    def has_revisions(self, revision_ids):
1099
1065
        """Probe to find out the presence of multiple revisions.
1100
1066
 
1101
1067
        :param revision_ids: An iterable of revision_ids.
1102
1068
        :return: A set of the revision_ids that were present.
1103
1069
        """
1104
 
        parent_map = self.revisions.get_parent_map(
1105
 
            [(rev_id,) for rev_id in revision_ids])
1106
 
        result = set()
1107
 
        if _mod_revision.NULL_REVISION in revision_ids:
1108
 
            result.add(_mod_revision.NULL_REVISION)
1109
 
        result.update([key[0] for key in parent_map])
1110
 
        return result
 
1070
        raise NotImplementedError(self.has_revisions)
 
1071
 
 
1072
        return self._revision_store.has_revision_id(revision_id,
 
1073
                                                    self.get_transaction())
1111
1074
 
1112
1075
    @needs_read_lock
1113
1076
    def get_revision(self, revision_id):
1136
1099
        for rev_id in revision_ids:
1137
1100
            if not rev_id or not isinstance(rev_id, basestring):
1138
1101
                raise errors.InvalidRevisionId(revision_id=rev_id, branch=self)
1139
 
        keys = [(key,) for key in revision_ids]
1140
 
        stream = self.revisions.get_record_stream(keys, 'unordered', True)
1141
 
        revs = {}
1142
 
        for record in stream:
1143
 
            if record.storage_kind == 'absent':
1144
 
                raise errors.NoSuchRevision(self, record.key[0])
1145
 
            text = record.get_bytes_as('fulltext')
1146
 
            rev = self._serializer.read_revision_from_string(text)
1147
 
            revs[record.key[0]] = rev
1148
 
        return [revs[revid] for revid in revision_ids]
 
1102
        revs = self._revision_store.get_revisions(revision_ids,
 
1103
                                                  self.get_transaction())
 
1104
        return revs
1149
1105
 
1150
1106
    @needs_read_lock
1151
1107
    def get_revision_xml(self, revision_id):
1153
1109
        #       would have already do it.
1154
1110
        # TODO: jam 20070210 Just use _serializer.write_revision_to_string()
1155
1111
        rev = self.get_revision(revision_id)
1156
 
        rev_tmp = cStringIO.StringIO()
 
1112
        rev_tmp = StringIO()
1157
1113
        # the current serializer..
1158
 
        self._serializer.write_revision(rev, rev_tmp)
 
1114
        self._revision_store._serializer.write_revision(rev, rev_tmp)
1159
1115
        rev_tmp.seek(0)
1160
1116
        return rev_tmp.getvalue()
1161
1117
 
1174
1130
                     t in self.revision_trees(required_trees))
1175
1131
        for revision in revisions:
1176
1132
            if not revision.parent_ids:
1177
 
                old_tree = self.revision_tree(_mod_revision.NULL_REVISION)
 
1133
                old_tree = self.revision_tree(None)
1178
1134
            else:
1179
1135
                old_tree = trees[revision.parent_ids[0]]
1180
1136
            yield trees[revision.revision_id].changes_from(old_tree)
1196
1152
 
1197
1153
    @needs_write_lock
1198
1154
    def add_signature_text(self, revision_id, signature):
1199
 
        self.signatures.add_lines((revision_id,), (),
1200
 
            osutils.split_lines(signature))
 
1155
        self._revision_store.add_revision_signature_text(revision_id,
 
1156
                                                         signature,
 
1157
                                                         self.get_transaction())
1201
1158
 
1202
1159
    def find_text_key_references(self):
1203
1160
        """Find the text key references within the repository.
1210
1167
            revision_id that they contain. The inventory texts from all present
1211
1168
            revision ids are assessed to generate this report.
1212
1169
        """
1213
 
        revision_keys = self.revisions.keys()
1214
 
        w = self.inventories
 
1170
        revision_ids = self.all_revision_ids()
 
1171
        w = self.get_inventory_weave()
1215
1172
        pb = ui.ui_factory.nested_progress_bar()
1216
1173
        try:
1217
1174
            return self._find_text_key_references_from_xml_inventory_lines(
1218
 
                w.iter_lines_added_or_present_in_keys(revision_keys, pb=pb))
 
1175
                w.iter_lines_added_or_present_in_versions(revision_ids, pb=pb))
1219
1176
        finally:
1220
1177
            pb.finished()
1221
1178
 
1258
1215
        search = self._file_ids_altered_regex.search
1259
1216
        unescape = _unescape_xml
1260
1217
        setdefault = result.setdefault
1261
 
        for line, line_key in line_iterator:
 
1218
        for line, version_id in line_iterator:
1262
1219
            match = search(line)
1263
1220
            if match is None:
1264
1221
                continue
1295
1252
 
1296
1253
            key = (file_id, revision_id)
1297
1254
            setdefault(key, False)
1298
 
            if revision_id == line_key[-1]:
 
1255
            if revision_id == version_id:
1299
1256
                result[key] = True
1300
1257
        return result
1301
1258
 
1316
1273
        """
1317
1274
        result = {}
1318
1275
        setdefault = result.setdefault
1319
 
        for key in \
 
1276
        for file_id, revision_id in \
1320
1277
            self._find_text_key_references_from_xml_inventory_lines(
1321
1278
                line_iterator).iterkeys():
1322
1279
            # once data is all ensured-consistent; then this is
1323
1280
            # if revision_id == version_id
1324
 
            if key[-1:] in revision_ids:
1325
 
                setdefault(key[0], set()).add(key[-1])
 
1281
            if revision_id in revision_ids:
 
1282
                setdefault(file_id, set()).add(revision_id)
1326
1283
        return result
1327
1284
 
1328
1285
    def fileids_altered_by_revision_ids(self, revision_ids, _inv_weave=None):
1335
1292
        revision_ids. Each altered file-ids has the exact revision_ids that
1336
1293
        altered it listed explicitly.
1337
1294
        """
1338
 
        selected_keys = set((revid,) for revid in revision_ids)
1339
 
        w = _inv_weave or self.inventories
 
1295
        selected_revision_ids = set(revision_ids)
 
1296
        w = _inv_weave or self.get_inventory_weave()
1340
1297
        pb = ui.ui_factory.nested_progress_bar()
1341
1298
        try:
1342
1299
            return self._find_file_ids_from_xml_inventory_lines(
1343
 
                w.iter_lines_added_or_present_in_keys(
1344
 
                    selected_keys, pb=pb),
1345
 
                selected_keys)
 
1300
                w.iter_lines_added_or_present_in_versions(
 
1301
                    selected_revision_ids, pb=pb),
 
1302
                selected_revision_ids)
1346
1303
        finally:
1347
1304
            pb.finished()
1348
1305
 
1359
1316
 
1360
1317
        bytes_iterator is an iterable of bytestrings for the file.  The
1361
1318
        kind of iterable and length of the bytestrings are unspecified, but for
1362
 
        this implementation, it is a list of bytes produced by
1363
 
        VersionedFile.get_record_stream().
 
1319
        this implementation, it is a list of lines produced by
 
1320
        VersionedFile.get_lines().
1364
1321
 
1365
1322
        :param desired_files: a list of (file_id, revision_id, identifier)
1366
1323
            triples
1367
1324
        """
1368
1325
        transaction = self.get_transaction()
1369
 
        text_keys = {}
1370
1326
        for file_id, revision_id, callable_data in desired_files:
1371
 
            text_keys[(file_id, revision_id)] = callable_data
1372
 
        for record in self.texts.get_record_stream(text_keys, 'unordered', True):
1373
 
            if record.storage_kind == 'absent':
1374
 
                raise errors.RevisionNotPresent(record.key, self)
1375
 
            yield text_keys[record.key], record.get_bytes_as('fulltext')
 
1327
            try:
 
1328
                weave = self.weave_store.get_weave(file_id, transaction)
 
1329
            except errors.NoSuchFile:
 
1330
                raise errors.NoSuchIdInRepository(self, file_id)
 
1331
            yield callable_data, weave.get_lines(revision_id)
1376
1332
 
1377
1333
    def _generate_text_key_index(self, text_key_references=None,
1378
1334
        ancestors=None):
1495
1451
        # maybe this generator should explicitly have the contract that it
1496
1452
        # should not be iterated until the previously yielded item has been
1497
1453
        # processed?
1498
 
        inv_w = self.inventories
 
1454
        inv_w = self.get_inventory_weave()
1499
1455
 
1500
1456
        # file ids that changed
1501
1457
        file_ids = self.fileids_altered_by_revision_ids(revision_ids, inv_w)
1529
1485
        yield ("revisions", None, revision_ids)
1530
1486
 
1531
1487
    @needs_read_lock
 
1488
    def get_inventory_weave(self):
 
1489
        return self.control_weaves.get_weave('inventory',
 
1490
            self.get_transaction())
 
1491
 
 
1492
    @needs_read_lock
1532
1493
    def get_inventory(self, revision_id):
1533
1494
        """Get Inventory object by revision id."""
1534
1495
        return self.iter_inventories([revision_id]).next()
1549
1510
 
1550
1511
    def _iter_inventories(self, revision_ids):
1551
1512
        """single-document based inventory iteration."""
1552
 
        for text, revision_id in self._iter_inventory_xmls(revision_ids):
 
1513
        texts = self.get_inventory_weave().get_texts(revision_ids)
 
1514
        for text, revision_id in zip(texts, revision_ids):
1553
1515
            yield self.deserialise_inventory(revision_id, text)
1554
1516
 
1555
 
    def _iter_inventory_xmls(self, revision_ids):
1556
 
        keys = [(revision_id,) for revision_id in revision_ids]
1557
 
        stream = self.inventories.get_record_stream(keys, 'unordered', True)
1558
 
        texts = {}
1559
 
        for record in stream:
1560
 
            if record.storage_kind != 'absent':
1561
 
                texts[record.key] = record.get_bytes_as('fulltext')
1562
 
            else:
1563
 
                raise errors.NoSuchRevision(self, record.key)
1564
 
        for key in keys:
1565
 
            yield texts[key], key[-1]
1566
 
 
1567
1517
    def deserialise_inventory(self, revision_id, xml):
1568
1518
        """Transform the xml into an inventory object. 
1569
1519
 
1588
1538
    @needs_read_lock
1589
1539
    def get_inventory_xml(self, revision_id):
1590
1540
        """Get inventory XML as a file object."""
1591
 
        texts = self._iter_inventory_xmls([revision_id])
1592
1541
        try:
1593
 
            text, revision_id = texts.next()
1594
 
        except StopIteration:
 
1542
            iw = self.get_inventory_weave()
 
1543
            return iw.get_text(revision_id)
 
1544
        except IndexError:
1595
1545
            raise errors.HistoryMissing(self, 'inventory', revision_id)
1596
 
        return text
1597
1546
 
1598
1547
    @needs_read_lock
1599
1548
    def get_inventory_sha1(self, revision_id):
1640
1589
        else:
1641
1590
            return self.get_inventory(revision_id)
1642
1591
 
 
1592
    @needs_read_lock
1643
1593
    def is_shared(self):
1644
1594
        """Return True if this repository is flagged as a shared repository."""
1645
1595
        raise NotImplementedError(self.is_shared)
1666
1616
    def revision_tree(self, revision_id):
1667
1617
        """Return Tree for a revision on this branch.
1668
1618
 
1669
 
        `revision_id` may be NULL_REVISION for the empty tree revision.
 
1619
        `revision_id` may be None for the empty tree revision.
1670
1620
        """
1671
 
        revision_id = _mod_revision.ensure_null(revision_id)
1672
1621
        # TODO: refactor this to use an existing revision object
1673
1622
        # so we don't need to read it in twice.
1674
 
        if revision_id == _mod_revision.NULL_REVISION:
 
1623
        if revision_id is None or revision_id == _mod_revision.NULL_REVISION:
1675
1624
            return RevisionTree(self, Inventory(root_id=None), 
1676
1625
                                _mod_revision.NULL_REVISION)
1677
1626
        else:
1700
1649
            return [None]
1701
1650
        if not self.has_revision(revision_id):
1702
1651
            raise errors.NoSuchRevision(self, revision_id)
1703
 
        graph = self.get_graph()
1704
 
        keys = set()
1705
 
        search = graph._make_breadth_first_searcher([revision_id])
1706
 
        while True:
1707
 
            try:
1708
 
                found, ghosts = search.next_with_ghosts()
1709
 
            except StopIteration:
1710
 
                break
1711
 
            keys.update(found)
1712
 
        if _mod_revision.NULL_REVISION in keys:
1713
 
            keys.remove(_mod_revision.NULL_REVISION)
1714
 
        if topo_sorted:
1715
 
            parent_map = graph.get_parent_map(keys)
1716
 
            keys = tsort.topo_sort(parent_map)
1717
 
        return [None] + list(keys)
 
1652
        w = self.get_inventory_weave()
 
1653
        candidates = w.get_ancestry(revision_id, topo_sorted)
 
1654
        return [None] + candidates # self._eliminate_revisions_not_present(candidates)
1718
1655
 
1719
1656
    def pack(self):
1720
1657
        """Compress the data within the repository.
1728
1665
        """
1729
1666
 
1730
1667
    @needs_read_lock
1731
 
    @deprecated_method(one_six)
1732
1668
    def print_file(self, file, revision_id):
1733
1669
        """Print `file` to stdout.
1734
1670
        
1749
1685
    def get_transaction(self):
1750
1686
        return self.control_files.get_transaction()
1751
1687
 
1752
 
    @deprecated_method(one_one)
 
1688
    @deprecated_method(symbol_versioning.one_one)
1753
1689
    def get_parents(self, revision_ids):
1754
1690
        """See StackedParentsProvider.get_parents"""
1755
1691
        parent_map = self.get_parent_map(revision_ids)
1756
1692
        return [parent_map.get(r, None) for r in revision_ids]
1757
1693
 
1758
 
    def get_parent_map(self, revision_ids):
 
1694
    def get_parent_map(self, keys):
1759
1695
        """See graph._StackedParentsProvider.get_parent_map"""
1760
 
        # revisions index works in keys; this just works in revisions
1761
 
        # therefore wrap and unwrap
1762
 
        query_keys = []
1763
 
        result = {}
1764
 
        for revision_id in revision_ids:
 
1696
        parent_map = {}
 
1697
        for revision_id in keys:
 
1698
            if revision_id is None:
 
1699
                raise ValueError('get_parent_map(None) is not valid')
1765
1700
            if revision_id == _mod_revision.NULL_REVISION:
1766
 
                result[revision_id] = ()
1767
 
            elif revision_id is None:
1768
 
                raise ValueError('get_parent_map(None) is not valid')
1769
 
            else:
1770
 
                query_keys.append((revision_id ,))
1771
 
        for ((revision_id,), parent_keys) in \
1772
 
                self.revisions.get_parent_map(query_keys).iteritems():
1773
 
            if parent_keys:
1774
 
                result[revision_id] = tuple(parent_revid
1775
 
                    for (parent_revid,) in parent_keys)
1776
 
            else:
1777
 
                result[revision_id] = (_mod_revision.NULL_REVISION,)
1778
 
        return result
 
1701
                parent_map[revision_id] = ()
 
1702
            else:
 
1703
                try:
 
1704
                    parent_id_list = self.get_revision(revision_id).parent_ids
 
1705
                except errors.NoSuchRevision:
 
1706
                    pass
 
1707
                else:
 
1708
                    if len(parent_id_list) == 0:
 
1709
                        parent_ids = (_mod_revision.NULL_REVISION,)
 
1710
                    else:
 
1711
                        parent_ids = tuple(parent_id_list)
 
1712
                    parent_map[revision_id] = parent_ids
 
1713
        return parent_map
1779
1714
 
1780
1715
    def _make_parents_provider(self):
1781
1716
        return self
1830
1765
    @needs_read_lock
1831
1766
    def has_signature_for_revision_id(self, revision_id):
1832
1767
        """Query for a revision signature for revision_id in the repository."""
1833
 
        if not self.has_revision(revision_id):
1834
 
            raise errors.NoSuchRevision(self, revision_id)
1835
 
        sig_present = (1 == len(
1836
 
            self.signatures.get_parent_map([(revision_id,)])))
1837
 
        return sig_present
 
1768
        return self._revision_store.has_signature(revision_id,
 
1769
                                                  self.get_transaction())
1838
1770
 
1839
1771
    @needs_read_lock
1840
1772
    def get_signature_text(self, revision_id):
1841
1773
        """Return the text for a signature."""
1842
 
        stream = self.signatures.get_record_stream([(revision_id,)],
1843
 
            'unordered', True)
1844
 
        record = stream.next()
1845
 
        if record.storage_kind == 'absent':
1846
 
            raise errors.NoSuchRevision(self, revision_id)
1847
 
        return record.get_bytes_as('fulltext')
 
1774
        return self._revision_store.get_signature_text(revision_id,
 
1775
                                                       self.get_transaction())
1848
1776
 
1849
1777
    @needs_read_lock
1850
1778
    def check(self, revision_ids=None):
1968
1896
            present_parents.append(p_id)
1969
1897
            parent_trees[p_id] = repository.revision_tree(p_id)
1970
1898
        else:
1971
 
            parent_trees[p_id] = repository.revision_tree(
1972
 
                                     _mod_revision.NULL_REVISION)
 
1899
            parent_trees[p_id] = repository.revision_tree(None)
1973
1900
 
1974
1901
    inv = revision_tree.inventory
1975
1902
    entries = inv.iter_entries()
1978
1905
        path, root = entries.next()
1979
1906
        if root.revision != rev.revision_id:
1980
1907
            raise errors.IncompatibleRevision(repr(repository))
1981
 
    text_keys = {}
 
1908
    # Add the texts that are not already present
1982
1909
    for path, ie in entries:
1983
 
        text_keys[(ie.file_id, ie.revision)] = ie
1984
 
    text_parent_map = repository.texts.get_parent_map(text_keys)
1985
 
    missing_texts = set(text_keys) - set(text_parent_map)
1986
 
    # Add the texts that are not already present
1987
 
    for text_key in missing_texts:
1988
 
        ie = text_keys[text_key]
1989
 
        text_parents = []
1990
 
        # FIXME: TODO: The following loop overlaps/duplicates that done by
1991
 
        # commit to determine parents. There is a latent/real bug here where
1992
 
        # the parents inserted are not those commit would do - in particular
1993
 
        # they are not filtered by heads(). RBC, AB
1994
 
        for revision, tree in parent_trees.iteritems():
1995
 
            if ie.file_id not in tree:
1996
 
                continue
1997
 
            parent_id = tree.inventory[ie.file_id].revision
1998
 
            if parent_id in text_parents:
1999
 
                continue
2000
 
            text_parents.append((ie.file_id, parent_id))
2001
 
        lines = revision_tree.get_file(ie.file_id).readlines()
2002
 
        repository.texts.add_lines(text_key, text_parents, lines)
 
1910
        w = repository.weave_store.get_weave_or_empty(ie.file_id,
 
1911
                repository.get_transaction())
 
1912
        if ie.revision not in w:
 
1913
            text_parents = []
 
1914
            # FIXME: TODO: The following loop *may* be overlapping/duplicate
 
1915
            # with InventoryEntry.find_previous_heads(). if it is, then there
 
1916
            # is a latent bug here where the parents may have ancestors of each
 
1917
            # other. RBC, AB
 
1918
            for revision, tree in parent_trees.iteritems():
 
1919
                if ie.file_id not in tree:
 
1920
                    continue
 
1921
                parent_id = tree.inventory[ie.file_id].revision
 
1922
                if parent_id in text_parents:
 
1923
                    continue
 
1924
                text_parents.append(parent_id)
 
1925
                    
 
1926
            vfile = repository.weave_store.get_weave_or_empty(ie.file_id, 
 
1927
                repository.get_transaction())
 
1928
            lines = revision_tree.get_file(ie.file_id).readlines()
 
1929
            vfile.add_lines(rev.revision_id, text_parents, lines)
2003
1930
    try:
2004
1931
        # install the inventory
2005
1932
        repository.add_inventory(rev.revision_id, inv, present_parents)
2017
1944
        typically pointing to .bzr/repository.
2018
1945
    """
2019
1946
 
2020
 
    def __init__(self, _format, a_bzrdir, control_files):
2021
 
        super(MetaDirRepository, self).__init__(_format, a_bzrdir, control_files)
 
1947
    def __init__(self, _format, a_bzrdir, control_files, _revision_store, control_store, text_store):
 
1948
        super(MetaDirRepository, self).__init__(_format,
 
1949
                                                a_bzrdir,
 
1950
                                                control_files,
 
1951
                                                _revision_store,
 
1952
                                                control_store,
 
1953
                                                text_store)
2022
1954
        self._transport = control_files._transport
2023
1955
 
 
1956
    @needs_read_lock
2024
1957
    def is_shared(self):
2025
1958
        """Return True if this repository is flagged as a shared repository."""
2026
1959
        return self._transport.has('shared-storage')
2052
1985
class MetaDirVersionedFileRepository(MetaDirRepository):
2053
1986
    """Repositories in a meta-dir, that work via versioned file objects."""
2054
1987
 
2055
 
    def __init__(self, _format, a_bzrdir, control_files):
 
1988
    def __init__(self, _format, a_bzrdir, control_files, _revision_store, control_store, text_store):
2056
1989
        super(MetaDirVersionedFileRepository, self).__init__(_format, a_bzrdir,
2057
 
            control_files)
 
1990
            control_files, _revision_store, control_store, text_store)
 
1991
        _revision_store.get_scope = self.get_transaction
 
1992
        control_store.get_scope = self.get_transaction
 
1993
        text_store.get_scope = self.get_transaction
2058
1994
 
2059
1995
 
2060
1996
class RepositoryFormatRegistry(registry.Registry):
2156
2092
        from bzrlib import bzrdir
2157
2093
        return bzrdir.format_registry.make_bzrdir('default').repository_format
2158
2094
 
 
2095
    def _get_control_store(self, repo_transport, control_files):
 
2096
        """Return the control store for this repository."""
 
2097
        raise NotImplementedError(self._get_control_store)
 
2098
 
2159
2099
    def get_format_string(self):
2160
2100
        """Return the ASCII format string that identifies this format.
2161
2101
        
2168
2108
        """Return the short description for this format."""
2169
2109
        raise NotImplementedError(self.get_format_description)
2170
2110
 
 
2111
    def _get_revision_store(self, repo_transport, control_files):
 
2112
        """Return the revision store object for this a_bzrdir."""
 
2113
        raise NotImplementedError(self._get_revision_store)
 
2114
 
 
2115
    def _get_text_rev_store(self,
 
2116
                            transport,
 
2117
                            control_files,
 
2118
                            name,
 
2119
                            compressed=True,
 
2120
                            prefixed=False,
 
2121
                            serializer=None):
 
2122
        """Common logic for getting a revision store for a repository.
 
2123
        
 
2124
        see self._get_revision_store for the subclass-overridable method to 
 
2125
        get the store for a repository.
 
2126
        """
 
2127
        from bzrlib.store.revision.text import TextRevisionStore
 
2128
        dir_mode = control_files._dir_mode
 
2129
        file_mode = control_files._file_mode
 
2130
        text_store = TextStore(transport.clone(name),
 
2131
                              prefixed=prefixed,
 
2132
                              compressed=compressed,
 
2133
                              dir_mode=dir_mode,
 
2134
                              file_mode=file_mode)
 
2135
        _revision_store = TextRevisionStore(text_store, serializer)
 
2136
        return _revision_store
 
2137
 
2171
2138
    # TODO: this shouldn't be in the base class, it's specific to things that
2172
2139
    # use weaves or knits -- mbp 20070207
2173
2140
    def _get_versioned_file_store(self,
2310
2277
    'bzrlib.repofmt.pack_repo',
2311
2278
    'RepositoryFormatKnitPack4',
2312
2279
    )
2313
 
format_registry.register_lazy(
2314
 
    'Bazaar RepositoryFormatKnitPack5 (bzr 1.6)\n',
2315
 
    'bzrlib.repofmt.pack_repo',
2316
 
    'RepositoryFormatKnitPack5',
2317
 
    )
2318
 
format_registry.register_lazy(
2319
 
    'Bazaar RepositoryFormatKnitPack5RichRoot (bzr 1.6.1)\n',
2320
 
    'bzrlib.repofmt.pack_repo',
2321
 
    'RepositoryFormatKnitPack5RichRoot',
2322
 
    )
2323
 
format_registry.register_lazy(
2324
 
    'Bazaar RepositoryFormatKnitPack5RichRoot (bzr 1.6)\n',
2325
 
    'bzrlib.repofmt.pack_repo',
2326
 
    'RepositoryFormatKnitPack5RichRootBroken',
2327
 
    )
2328
 
 
2329
2280
# Development formats. 
2330
 
# 1.7->1.8 go below here
2331
 
format_registry.register_lazy(
2332
 
    "Bazaar development format 2 (needs bzr.dev from before 1.8)\n",
2333
 
    'bzrlib.repofmt.pack_repo',
2334
 
    'RepositoryFormatPackDevelopment2',
2335
 
    )
2336
 
format_registry.register_lazy(
2337
 
    ("Bazaar development format 2 with subtree support "
2338
 
        "(needs bzr.dev from before 1.8)\n"),
2339
 
    'bzrlib.repofmt.pack_repo',
2340
 
    'RepositoryFormatPackDevelopment2Subtree',
2341
 
    )
 
2281
# 1.2->1.3
 
2282
# development 0 - stub to introduce development versioning scheme.
 
2283
format_registry.register_lazy(
 
2284
    "Bazaar development format 0 (needs bzr.dev from before 1.3)\n",
 
2285
    'bzrlib.repofmt.pack_repo',
 
2286
    'RepositoryFormatPackDevelopment0',
 
2287
    )
 
2288
format_registry.register_lazy(
 
2289
    ("Bazaar development format 0 with subtree support "
 
2290
        "(needs bzr.dev from before 1.3)\n"),
 
2291
    'bzrlib.repofmt.pack_repo',
 
2292
    'RepositoryFormatPackDevelopment0Subtree',
 
2293
    )
 
2294
# 1.3->1.4 go below here
2342
2295
 
2343
2296
 
2344
2297
class InterRepository(InterObject):
2353
2306
    InterRepository.get(other).method_name(parameters).
2354
2307
    """
2355
2308
 
2356
 
    _walk_to_common_revisions_batch_size = 1
2357
2309
    _optimisers = []
2358
2310
    """The available optimised InterRepository types."""
2359
2311
 
2360
 
    def __init__(self, source, target):
2361
 
        InterObject.__init__(self, source, target)
2362
 
        # These two attributes may be overridden by e.g. InterOtherToRemote to
2363
 
        # provide a faster implementation.
2364
 
        self.target_get_graph = self.target.get_graph
2365
 
        self.target_get_parent_map = self.target.get_parent_map
2366
 
 
2367
2312
    def copy_content(self, revision_id=None):
2368
2313
        raise NotImplementedError(self.copy_content)
2369
2314
 
2377
2322
        :param pb: optional progress bar to use for progress reports. If not
2378
2323
                   provided a default one will be created.
2379
2324
 
2380
 
        :returns: (copied_revision_count, failures).
 
2325
        Returns the copied revision count and the failed revisions in a tuple:
 
2326
        (copied, failures).
2381
2327
        """
2382
 
        # Normally we should find a specific InterRepository subclass to do
2383
 
        # the fetch; if nothing else then at least InterSameDataRepository.
2384
 
        # If none of them is suitable it looks like fetching is not possible;
2385
 
        # we try to give a good message why.  _assert_same_model will probably
2386
 
        # give a helpful message; otherwise a generic one.
2387
 
        self._assert_same_model(self.source, self.target)
2388
 
        raise errors.IncompatibleRepositories(self.source, self.target,
2389
 
            "no suitableInterRepository found")
 
2328
        raise NotImplementedError(self.fetch)
2390
2329
 
2391
2330
    def _walk_to_common_revisions(self, revision_ids):
2392
2331
        """Walk out from revision_ids in source to revisions target has.
2394
2333
        :param revision_ids: The start point for the search.
2395
2334
        :return: A set of revision ids.
2396
2335
        """
2397
 
        target_graph = self.target_get_graph()
 
2336
        target_graph = self.target.get_graph()
2398
2337
        revision_ids = frozenset(revision_ids)
2399
 
        # Fast path for the case where all the revisions are already in the
2400
 
        # target repo.
2401
 
        # (Although this does incur an extra round trip for the
2402
 
        # fairly common case where the target doesn't already have the revision
2403
 
        # we're pushing.)
2404
2338
        if set(target_graph.get_parent_map(revision_ids)) == revision_ids:
2405
2339
            return graph.SearchResult(revision_ids, set(), 0, set())
2406
2340
        missing_revs = set()
2408
2342
        # ensure we don't pay silly lookup costs.
2409
2343
        searcher = source_graph._make_breadth_first_searcher(revision_ids)
2410
2344
        null_set = frozenset([_mod_revision.NULL_REVISION])
2411
 
        searcher_exhausted = False
2412
2345
        while True:
2413
 
            next_revs = set()
2414
 
            ghosts = set()
2415
 
            # Iterate the searcher until we have enough next_revs
2416
 
            while len(next_revs) < self._walk_to_common_revisions_batch_size:
2417
 
                try:
2418
 
                    next_revs_part, ghosts_part = searcher.next_with_ghosts()
2419
 
                    next_revs.update(next_revs_part)
2420
 
                    ghosts.update(ghosts_part)
2421
 
                except StopIteration:
2422
 
                    searcher_exhausted = True
2423
 
                    break
2424
 
            # If there are ghosts in the source graph, and the caller asked for
2425
 
            # them, make sure that they are present in the target.
2426
 
            # We don't care about other ghosts as we can't fetch them and
 
2346
            try:
 
2347
                next_revs, ghosts = searcher.next_with_ghosts()
 
2348
            except StopIteration:
 
2349
                break
 
2350
            if revision_ids.intersection(ghosts):
 
2351
                absent_ids = set(revision_ids.intersection(ghosts))
 
2352
                # If all absent_ids are present in target, no error is needed.
 
2353
                absent_ids.difference_update(
 
2354
                    set(target_graph.get_parent_map(absent_ids)))
 
2355
                if absent_ids:
 
2356
                    raise errors.NoSuchRevision(self.source, absent_ids.pop())
 
2357
            # we don't care about other ghosts as we can't fetch them and
2427
2358
            # haven't been asked to.
2428
 
            ghosts_to_check = set(revision_ids.intersection(ghosts))
2429
 
            revs_to_get = set(next_revs).union(ghosts_to_check)
2430
 
            if revs_to_get:
2431
 
                have_revs = set(target_graph.get_parent_map(revs_to_get))
2432
 
                # we always have NULL_REVISION present.
2433
 
                have_revs = have_revs.union(null_set)
2434
 
                # Check if the target is missing any ghosts we need.
2435
 
                ghosts_to_check.difference_update(have_revs)
2436
 
                if ghosts_to_check:
2437
 
                    # One of the caller's revision_ids is a ghost in both the
2438
 
                    # source and the target.
2439
 
                    raise errors.NoSuchRevision(
2440
 
                        self.source, ghosts_to_check.pop())
2441
 
                missing_revs.update(next_revs - have_revs)
2442
 
                searcher.stop_searching_any(have_revs)
2443
 
            if searcher_exhausted:
2444
 
                break
 
2359
            next_revs = set(next_revs)
 
2360
            # we always have NULL_REVISION present.
 
2361
            have_revs = set(target_graph.get_parent_map(next_revs)).union(null_set)
 
2362
            missing_revs.update(next_revs - have_revs)
 
2363
            searcher.stop_searching_any(have_revs)
2445
2364
        return searcher.get_result()
2446
2365
   
2447
 
    @deprecated_method(one_two)
 
2366
    @deprecated_method(symbol_versioning.one_two)
2448
2367
    @needs_read_lock
2449
2368
    def missing_revision_ids(self, revision_id=None, find_ghosts=True):
2450
2369
        """Return the revision ids that source has that target does not.
2486
2405
 
2487
2406
    @staticmethod
2488
2407
    def _same_model(source, target):
2489
 
        """True if source and target have the same data representation.
2490
 
        
2491
 
        Note: this is always called on the base class; overriding it in a
2492
 
        subclass will have no effect.
2493
 
        """
2494
 
        try:
2495
 
            InterRepository._assert_same_model(source, target)
2496
 
            return True
2497
 
        except errors.IncompatibleRepositories, e:
 
2408
        """True if source and target have the same data representation."""
 
2409
        if source.supports_rich_root() != target.supports_rich_root():
2498
2410
            return False
2499
 
 
2500
 
    @staticmethod
2501
 
    def _assert_same_model(source, target):
2502
 
        """Raise an exception if two repositories do not use the same model.
2503
 
        """
2504
 
        if source.supports_rich_root() != target.supports_rich_root():
2505
 
            raise errors.IncompatibleRepositories(source, target,
2506
 
                "different rich-root support")
2507
2411
        if source._serializer != target._serializer:
2508
 
            raise errors.IncompatibleRepositories(source, target,
2509
 
                "different serializers")
 
2412
            return False
 
2413
        return True
2510
2414
 
2511
2415
 
2512
2416
class InterSameDataRepository(InterRepository):
2555
2459
    @needs_write_lock
2556
2460
    def fetch(self, revision_id=None, pb=None, find_ghosts=False):
2557
2461
        """See InterRepository.fetch()."""
2558
 
        from bzrlib.fetch import RepoFetcher
 
2462
        from bzrlib.fetch import GenericRepoFetcher
2559
2463
        mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
2560
2464
               self.source, self.source._format, self.target,
2561
2465
               self.target._format)
2562
 
        f = RepoFetcher(to_repository=self.target,
 
2466
        f = GenericRepoFetcher(to_repository=self.target,
2563
2467
                               from_repository=self.source,
2564
2468
                               last_revision=revision_id,
2565
2469
                               pb=pb, find_ghosts=find_ghosts)
2613
2517
        if self.source._transport.listable():
2614
2518
            pb = ui.ui_factory.nested_progress_bar()
2615
2519
            try:
2616
 
                self.target.texts.insert_record_stream(
2617
 
                    self.source.texts.get_record_stream(
2618
 
                        self.source.texts.keys(), 'topological', False))
 
2520
                self.target.weave_store.copy_all_ids(
 
2521
                    self.source.weave_store,
 
2522
                    pb=pb,
 
2523
                    from_transaction=self.source.get_transaction(),
 
2524
                    to_transaction=self.target.get_transaction())
2619
2525
                pb.update('copying inventory', 0, 1)
2620
 
                self.target.inventories.insert_record_stream(
2621
 
                    self.source.inventories.get_record_stream(
2622
 
                        self.source.inventories.keys(), 'topological', False))
2623
 
                self.target.signatures.insert_record_stream(
2624
 
                    self.source.signatures.get_record_stream(
2625
 
                        self.source.signatures.keys(),
2626
 
                        'unordered', True))
2627
 
                self.target.revisions.insert_record_stream(
2628
 
                    self.source.revisions.get_record_stream(
2629
 
                        self.source.revisions.keys(),
2630
 
                        'topological', True))
 
2526
                self.target.control_weaves.copy_multi(
 
2527
                    self.source.control_weaves, ['inventory'],
 
2528
                    from_transaction=self.source.get_transaction(),
 
2529
                    to_transaction=self.target.get_transaction())
 
2530
                self.target._revision_store.text_store.copy_all_ids(
 
2531
                    self.source._revision_store.text_store,
 
2532
                    pb=pb)
2631
2533
            finally:
2632
2534
                pb.finished()
2633
2535
        else:
2636
2538
    @needs_write_lock
2637
2539
    def fetch(self, revision_id=None, pb=None, find_ghosts=False):
2638
2540
        """See InterRepository.fetch()."""
2639
 
        from bzrlib.fetch import RepoFetcher
 
2541
        from bzrlib.fetch import GenericRepoFetcher
2640
2542
        mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
2641
2543
               self.source, self.source._format, self.target, self.target._format)
2642
 
        f = RepoFetcher(to_repository=self.target,
 
2544
        f = GenericRepoFetcher(to_repository=self.target,
2643
2545
                               from_repository=self.source,
2644
2546
                               last_revision=revision_id,
2645
2547
                               pb=pb, find_ghosts=find_ghosts)
2717
2619
    @needs_write_lock
2718
2620
    def fetch(self, revision_id=None, pb=None, find_ghosts=False):
2719
2621
        """See InterRepository.fetch()."""
2720
 
        from bzrlib.fetch import RepoFetcher
 
2622
        from bzrlib.fetch import KnitRepoFetcher
2721
2623
        mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
2722
2624
               self.source, self.source._format, self.target, self.target._format)
2723
 
        f = RepoFetcher(to_repository=self.target,
 
2625
        f = KnitRepoFetcher(to_repository=self.target,
2724
2626
                            from_repository=self.source,
2725
2627
                            last_revision=revision_id,
2726
2628
                            pb=pb, find_ghosts=find_ghosts)
2787
2689
    @needs_write_lock
2788
2690
    def fetch(self, revision_id=None, pb=None, find_ghosts=False):
2789
2691
        """See InterRepository.fetch()."""
2790
 
        if (len(self.source._fallback_repositories) > 0 or
2791
 
            len(self.target._fallback_repositories) > 0):
2792
 
            # The pack layer is not aware of fallback repositories, so when
2793
 
            # fetching from a stacked repository or into a stacked repository
2794
 
            # we use the generic fetch logic which uses the VersionedFiles
2795
 
            # attributes on repository.
2796
 
            from bzrlib.fetch import RepoFetcher
2797
 
            fetcher = RepoFetcher(self.target, self.source, revision_id,
2798
 
                                  pb, find_ghosts)
2799
 
            return fetcher.count_copied, fetcher.failed_revisions
2800
2692
        from bzrlib.repofmt.pack_repo import Packer
2801
2693
        mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
2802
2694
               self.source, self.source._format, self.target, self.target._format)
2807
2699
            # to fetch from all packs to one without
2808
2700
            # inventory parsing etc, IFF nothing to be copied is in the target.
2809
2701
            # till then:
2810
 
            source_revision_ids = frozenset(self.source.all_revision_ids())
2811
 
            revision_ids = source_revision_ids - \
2812
 
                frozenset(self.target_get_parent_map(source_revision_ids))
 
2702
            revision_ids = self.source.all_revision_ids()
2813
2703
            revision_keys = [(revid,) for revid in revision_ids]
2814
2704
            index = self.target._pack_collection.revision_index.combined_index
2815
2705
            present_revision_ids = set(item[1][0] for item in
2856
2746
        if not find_ghosts and revision_id is not None:
2857
2747
            return self._walk_to_common_revisions([revision_id])
2858
2748
        elif revision_id is not None:
2859
 
            # Find ghosts: search for revisions pointing from one repository to
2860
 
            # the other, and vice versa, anywhere in the history of revision_id.
2861
 
            graph = self.target_get_graph(other_repository=self.source)
2862
 
            searcher = graph._make_breadth_first_searcher([revision_id])
2863
 
            found_ids = set()
2864
 
            while True:
2865
 
                try:
2866
 
                    next_revs, ghosts = searcher.next_with_ghosts()
2867
 
                except StopIteration:
2868
 
                    break
2869
 
                if revision_id in ghosts:
2870
 
                    raise errors.NoSuchRevision(self.source, revision_id)
2871
 
                found_ids.update(next_revs)
2872
 
                found_ids.update(ghosts)
2873
 
            found_ids = frozenset(found_ids)
2874
 
            # Double query here: should be able to avoid this by changing the
2875
 
            # graph api further.
2876
 
            result_set = found_ids - frozenset(
2877
 
                self.target_get_parent_map(found_ids))
 
2749
            source_ids = self.source.get_ancestry(revision_id)
 
2750
            if source_ids[0] is not None:
 
2751
                raise AssertionError()
 
2752
            source_ids.pop(0)
2878
2753
        else:
2879
2754
            source_ids = self.source.all_revision_ids()
2880
 
            # source_ids is the worst possible case we may need to pull.
2881
 
            # now we want to filter source_ids against what we actually
2882
 
            # have in target, but don't try to check for existence where we know
2883
 
            # we do not have a revision as that would be pointless.
2884
 
            target_ids = set(self.target.all_revision_ids())
2885
 
            result_set = set(source_ids).difference(target_ids)
 
2755
        # source_ids is the worst possible case we may need to pull.
 
2756
        # now we want to filter source_ids against what we actually
 
2757
        # have in target, but don't try to check for existence where we know
 
2758
        # we do not have a revision as that would be pointless.
 
2759
        target_ids = set(self.target.all_revision_ids())
 
2760
        result_set = set(source_ids).difference(target_ids)
2886
2761
        return self.source.revision_ids_to_search_result(result_set)
2887
2762
 
2888
2763
 
2939
2814
    @staticmethod
2940
2815
    def is_compatible(source, target):
2941
2816
        """Be compatible with Knit1 source and Knit3 target"""
 
2817
        from bzrlib.repofmt.knitrepo import RepositoryFormatKnit3
2942
2818
        try:
2943
 
            from bzrlib.repofmt.knitrepo import (
 
2819
            from bzrlib.repofmt.knitrepo import (RepositoryFormatKnit1,
 
2820
                RepositoryFormatKnit3)
 
2821
            from bzrlib.repofmt.pack_repo import (
 
2822
                RepositoryFormatKnitPack1,
 
2823
                RepositoryFormatKnitPack3,
 
2824
                RepositoryFormatPackDevelopment0,
 
2825
                RepositoryFormatPackDevelopment0Subtree,
 
2826
                )
 
2827
            nosubtrees = (
2944
2828
                RepositoryFormatKnit1,
 
2829
                RepositoryFormatKnitPack1,
 
2830
                RepositoryFormatPackDevelopment0,
 
2831
                )
 
2832
            subtrees = (
2945
2833
                RepositoryFormatKnit3,
2946
 
                )
2947
 
            from bzrlib.repofmt.pack_repo import (
2948
 
                RepositoryFormatKnitPack1,
2949
2834
                RepositoryFormatKnitPack3,
2950
 
                RepositoryFormatKnitPack4,
2951
 
                RepositoryFormatKnitPack5,
2952
 
                RepositoryFormatKnitPack5RichRoot,
2953
 
                RepositoryFormatPackDevelopment2,
2954
 
                RepositoryFormatPackDevelopment2Subtree,
2955
 
                )
2956
 
            norichroot = (
2957
 
                RepositoryFormatKnit1,            # no rr, no subtree
2958
 
                RepositoryFormatKnitPack1,        # no rr, no subtree
2959
 
                RepositoryFormatPackDevelopment2, # no rr, no subtree
2960
 
                RepositoryFormatKnitPack5,        # no rr, no subtree
2961
 
                )
2962
 
            richroot = (
2963
 
                RepositoryFormatKnit3,            # rr, subtree
2964
 
                RepositoryFormatKnitPack3,        # rr, subtree
2965
 
                RepositoryFormatKnitPack4,        # rr, no subtree
2966
 
                RepositoryFormatKnitPack5RichRoot,# rr, no subtree
2967
 
                RepositoryFormatPackDevelopment2Subtree, # rr, subtree
2968
 
                )
2969
 
            for format in norichroot:
2970
 
                if format.rich_root_data:
2971
 
                    raise AssertionError('Format %s is a rich-root format'
2972
 
                        ' but is included in the non-rich-root list'
2973
 
                        % (format,))
2974
 
            for format in richroot:
2975
 
                if not format.rich_root_data:
2976
 
                    raise AssertionError('Format %s is not a rich-root format'
2977
 
                        ' but is included in the rich-root list'
2978
 
                        % (format,))
2979
 
            # TODO: One alternative is to just check format.rich_root_data,
2980
 
            #       instead of keeping membership lists. However, the formats
2981
 
            #       *also* have to use the same 'Knit' style of storage
2982
 
            #       (line-deltas, fulltexts, etc.)
2983
 
            return (isinstance(source._format, norichroot) and
2984
 
                    isinstance(target._format, richroot))
 
2835
                RepositoryFormatPackDevelopment0Subtree,
 
2836
                )
 
2837
            return (isinstance(source._format, nosubtrees) and
 
2838
                isinstance(target._format, subtrees))
2985
2839
        except AttributeError:
2986
2840
            return False
2987
2841
 
3048
2902
        return len(revision_ids), 0
3049
2903
 
3050
2904
 
3051
 
class InterOtherToRemote(InterRepository):
3052
 
 
3053
 
    _walk_to_common_revisions_batch_size = 50
3054
 
 
3055
 
    def __init__(self, source, target):
3056
 
        InterRepository.__init__(self, source, target)
3057
 
        self._real_inter = None
3058
 
 
3059
 
    @staticmethod
3060
 
    def is_compatible(source, target):
3061
 
        if isinstance(target, remote.RemoteRepository):
3062
 
            return True
3063
 
        return False
3064
 
 
3065
 
    def _ensure_real_inter(self):
3066
 
        if self._real_inter is None:
3067
 
            self.target._ensure_real()
3068
 
            real_target = self.target._real_repository
3069
 
            self._real_inter = InterRepository.get(self.source, real_target)
3070
 
            # Make _real_inter use the RemoteRepository for get_parent_map
3071
 
            self._real_inter.target_get_graph = self.target.get_graph
3072
 
            self._real_inter.target_get_parent_map = self.target.get_parent_map
3073
 
    
3074
 
    def copy_content(self, revision_id=None):
3075
 
        self._ensure_real_inter()
3076
 
        self._real_inter.copy_content(revision_id=revision_id)
3077
 
 
3078
 
    def fetch(self, revision_id=None, pb=None, find_ghosts=False):
3079
 
        self._ensure_real_inter()
3080
 
        return self._real_inter.fetch(revision_id=revision_id, pb=pb,
3081
 
            find_ghosts=find_ghosts)
3082
 
 
3083
 
    @classmethod
3084
 
    def _get_repo_format_to_test(self):
3085
 
        return None
3086
 
 
3087
 
 
3088
2905
class InterRemoteToOther(InterRepository):
3089
2906
 
3090
2907
    def __init__(self, source, target):
3103
2920
                "We don't support remote repos backed by remote repos yet.")
3104
2921
        return InterRepository._same_model(real_source, target)
3105
2922
 
 
2923
    @needs_write_lock
 
2924
    def fetch(self, revision_id=None, pb=None, find_ghosts=False):
 
2925
        """See InterRepository.fetch()."""
 
2926
        from bzrlib.fetch import RemoteToOtherFetcher
 
2927
        mutter("Using fetch logic to copy between %s(remote) and %s(%s)",
 
2928
               self.source, self.target, self.target._format)
 
2929
        # TODO: jam 20070210 This should be an assert, not a translate
 
2930
        revision_id = osutils.safe_revision_id(revision_id)
 
2931
        f = RemoteToOtherFetcher(to_repository=self.target,
 
2932
                                 from_repository=self.source,
 
2933
                                 last_revision=revision_id,
 
2934
                                 pb=pb, find_ghosts=find_ghosts)
 
2935
        return f.count_copied, f.failed_revisions
 
2936
 
 
2937
    @classmethod
 
2938
    def _get_repo_format_to_test(self):
 
2939
        return None
 
2940
 
 
2941
 
 
2942
class InterOtherToRemote(InterRepository):
 
2943
 
 
2944
    def __init__(self, source, target):
 
2945
        InterRepository.__init__(self, source, target)
 
2946
        self._real_inter = None
 
2947
 
 
2948
    @staticmethod
 
2949
    def is_compatible(source, target):
 
2950
        if isinstance(target, remote.RemoteRepository):
 
2951
            return True
 
2952
        return False
 
2953
 
3106
2954
    def _ensure_real_inter(self):
3107
2955
        if self._real_inter is None:
3108
 
            self.source._ensure_real()
3109
 
            real_source = self.source._real_repository
3110
 
            self._real_inter = InterRepository.get(real_source, self.target)
 
2956
            self.target._ensure_real()
 
2957
            real_target = self.target._real_repository
 
2958
            self._real_inter = InterRepository.get(self.source, real_target)
3111
2959
    
3112
 
    def fetch(self, revision_id=None, pb=None, find_ghosts=False):
3113
 
        self._ensure_real_inter()
3114
 
        return self._real_inter.fetch(revision_id=revision_id, pb=pb,
3115
 
            find_ghosts=find_ghosts)
3116
 
 
3117
2960
    def copy_content(self, revision_id=None):
3118
2961
        self._ensure_real_inter()
3119
2962
        self._real_inter.copy_content(revision_id=revision_id)
3120
2963
 
 
2964
    def fetch(self, revision_id=None, pb=None, find_ghosts=False):
 
2965
        self._ensure_real_inter()
 
2966
        self._real_inter.fetch(revision_id=revision_id, pb=pb,
 
2967
            find_ghosts=find_ghosts)
 
2968
 
3121
2969
    @classmethod
3122
2970
    def _get_repo_format_to_test(self):
3123
2971
        return None
3124
2972
 
3125
2973
 
3126
 
 
3127
2974
InterRepository.register_optimiser(InterDifferingSerializer)
3128
2975
InterRepository.register_optimiser(InterSameDataRepository)
3129
2976
InterRepository.register_optimiser(InterWeaveRepo)
3131
2978
InterRepository.register_optimiser(InterModel1and2)
3132
2979
InterRepository.register_optimiser(InterKnit1and2)
3133
2980
InterRepository.register_optimiser(InterPackRepo)
 
2981
InterRepository.register_optimiser(InterRemoteToOther)
3134
2982
InterRepository.register_optimiser(InterOtherToRemote)
3135
 
InterRepository.register_optimiser(InterRemoteToOther)
3136
2983
 
3137
2984
 
3138
2985
class CopyConverter(object):
3223
3070
        self.repository = repository
3224
3071
        self.text_index = self.repository._generate_text_key_index()
3225
3072
    
3226
 
    def calculate_file_version_parents(self, text_key):
 
3073
    def calculate_file_version_parents(self, revision_id, file_id):
3227
3074
        """Calculate the correct parents for a file version according to
3228
3075
        the inventories.
3229
3076
        """
3230
 
        parent_keys = self.text_index[text_key]
 
3077
        parent_keys = self.text_index[(file_id, revision_id)]
3231
3078
        if parent_keys == [_mod_revision.NULL_REVISION]:
3232
3079
            return ()
3233
 
        return tuple(parent_keys)
 
3080
        # strip the file_id, for the weave api
 
3081
        return tuple([revision_id for file_id, revision_id in parent_keys])
3234
3082
 
3235
 
    def check_file_version_parents(self, texts, progress_bar=None):
 
3083
    def check_file_version_parents(self, weave, file_id):
3236
3084
        """Check the parents stored in a versioned file are correct.
3237
3085
 
3238
3086
        It also detects file versions that are not referenced by their
3246
3094
            file, but not used by the corresponding inventory.
3247
3095
        """
3248
3096
        wrong_parents = {}
3249
 
        self.file_ids = set([file_id for file_id, _ in
3250
 
            self.text_index.iterkeys()])
3251
 
        # text keys is now grouped by file_id
3252
 
        n_weaves = len(self.file_ids)
3253
 
        files_in_revisions = {}
3254
 
        revisions_of_files = {}
3255
 
        n_versions = len(self.text_index)
3256
 
        progress_bar.update('loading text store', 0, n_versions)
3257
 
        parent_map = self.repository.texts.get_parent_map(self.text_index)
3258
 
        # On unlistable transports this could well be empty/error...
3259
 
        text_keys = self.repository.texts.keys()
3260
 
        unused_keys = frozenset(text_keys) - set(self.text_index)
3261
 
        for num, key in enumerate(self.text_index.iterkeys()):
3262
 
            if progress_bar is not None:
3263
 
                progress_bar.update('checking text graph', num, n_versions)
3264
 
            correct_parents = self.calculate_file_version_parents(key)
 
3097
        unused_versions = set()
 
3098
        versions = weave.versions()
 
3099
        parent_map = weave.get_parent_map(versions)
 
3100
        for num, revision_id in enumerate(versions):
3265
3101
            try:
3266
 
                knit_parents = parent_map[key]
3267
 
            except errors.RevisionNotPresent:
3268
 
                # Missing text!
3269
 
                knit_parents = None
3270
 
            if correct_parents != knit_parents:
3271
 
                wrong_parents[key] = (knit_parents, correct_parents)
3272
 
        return wrong_parents, unused_keys
 
3102
                correct_parents = self.calculate_file_version_parents(
 
3103
                    revision_id, file_id)
 
3104
            except KeyError:
 
3105
                # The version is not part of the used keys.
 
3106
                unused_versions.add(revision_id)
 
3107
            else:
 
3108
                try:
 
3109
                    knit_parents = tuple(parent_map[revision_id])
 
3110
                except errors.RevisionNotPresent:
 
3111
                    knit_parents = None
 
3112
                if correct_parents != knit_parents:
 
3113
                    wrong_parents[revision_id] = (knit_parents, correct_parents)
 
3114
        return wrong_parents, unused_versions
3273
3115
 
3274
3116
 
3275
3117
def _old_get_graph(repository, revision_id):