~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: 2007-11-03 01:53:30 UTC
  • mfrom: (2955.1.1 trunk)
  • Revision ID: pqm@pqm.ubuntu.com-20071103015330-pt1tec7wyxwwcey8
Fix #158972 don't use timeout for HttpServer

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
25
25
    bzrdir,
26
26
    check,
27
27
    debug,
 
28
    deprecated_graph,
28
29
    errors,
29
30
    generate_ids,
30
31
    gpg,
32
33
    lazy_regex,
33
34
    lockable_files,
34
35
    lockdir,
35
 
    lru_cache,
36
36
    osutils,
37
37
    registry,
38
38
    remote,
39
39
    revision as _mod_revision,
40
40
    symbol_versioning,
41
41
    transactions,
42
 
    tsort,
43
42
    ui,
44
43
    )
45
44
from bzrlib.bundle import serializer
55
54
from bzrlib.inventory import Inventory, InventoryDirectory, ROOT_ID
56
55
from bzrlib.symbol_versioning import (
57
56
        deprecated_method,
58
 
        one_one,
59
 
        one_two,
60
 
        one_three,
61
 
        one_six,
62
57
        )
63
58
from bzrlib.trace import mutter, mutter_callsite, note, warning
64
59
 
79
74
    # the default CommitBuilder does not manage trees whose root is versioned.
80
75
    _versioned_root = False
81
76
 
82
 
    def __init__(self, repository, parents, config, timestamp=None,
83
 
                 timezone=None, committer=None, revprops=None,
 
77
    def __init__(self, repository, parents, config, timestamp=None, 
 
78
                 timezone=None, committer=None, revprops=None, 
84
79
                 revision_id=None):
85
80
        """Initiate a CommitBuilder.
86
81
 
98
93
        if committer is None:
99
94
            self._committer = self._config.username()
100
95
        else:
 
96
            assert isinstance(committer, basestring), type(committer)
101
97
            self._committer = committer
102
98
 
103
99
        self.new_inventory = Inventory(None)
120
116
            self._timezone = int(timezone)
121
117
 
122
118
        self._generate_revision_if_needed()
123
 
        self.__heads = graph.HeadsCache(repository.get_graph()).heads
 
119
        self._heads = graph.HeadsCache(repository.get_graph()).heads
124
120
 
125
121
    def commit(self, message):
126
122
        """Make the actual commit.
162
158
        """Tell the builder that the inventory is finished."""
163
159
        if self.new_inventory.root is None:
164
160
            raise AssertionError('Root entry should be supplied to'
165
 
                ' record_entry_contents, as of bzr 0.10.')
 
161
                ' record_entry_contents, as of bzr 0.10.',
 
162
                 DeprecationWarning, stacklevel=2)
166
163
            self.new_inventory.add(InventoryDirectory(ROOT_ID, '', None))
167
164
        self.new_inventory.revision_id = self._new_revision_id
168
165
        self.inv_sha1 = self.repository.add_inventory(
191
188
        else:
192
189
            self.random_revid = False
193
190
 
194
 
    def _heads(self, file_id, revision_ids):
195
 
        """Calculate the graph heads for revision_ids in the graph of file_id.
196
 
 
197
 
        This can use either a per-file graph or a global revision graph as we
198
 
        have an identity relationship between the two graphs.
199
 
        """
200
 
        return self.__heads(revision_ids)
201
 
 
202
191
    def _check_root(self, ie, parent_invs, tree):
203
192
        """Helper for record_entry_contents.
204
193
 
291
280
            else:
292
281
                # we don't need to commit this, because the caller already
293
282
                # determined that an existing revision of this file is
294
 
                # appropriate. If its not being considered for committing then
295
 
                # it and all its parents to the root must be unaltered so
296
 
                # no-change against the basis.
297
 
                if ie.revision == self._new_revision_id:
298
 
                    raise AssertionError("Impossible situation, a skipped "
299
 
                        "inventory entry (%r) claims to be modified in this "
300
 
                        "commit (%r).", (ie, self._new_revision_id))
301
 
                return None, False
 
283
                # appropriate.
 
284
                return None, (ie.revision == self._new_revision_id)
302
285
        # XXX: Friction: parent_candidates should return a list not a dict
303
286
        #      so that we don't have to walk the inventories again.
304
287
        parent_candiate_entries = ie.parent_candidates(parent_invs)
305
 
        head_set = self._heads(ie.file_id, parent_candiate_entries.keys())
 
288
        head_set = self._heads(parent_candiate_entries.keys())
306
289
        heads = []
307
290
        for inv in parent_invs:
308
291
            if ie.file_id in inv:
335
318
            if kind != parent_entry.kind:
336
319
                store = True
337
320
        if kind == 'file':
338
 
            if content_summary[2] is None:
339
 
                raise ValueError("Files must not have executable = None")
 
321
            assert content_summary[2] is not None, \
 
322
                "Files must not have executable = None"
340
323
            if not store:
341
324
                if (# if the file length changed we have to store:
342
325
                    parent_entry.text_size != content_summary[1] or
417
400
        return self._get_delta(ie, basis_inv, path), True
418
401
 
419
402
    def _add_text_to_weave(self, file_id, new_lines, parents, nostore_sha):
 
403
        versionedfile = self.repository.weave_store.get_weave_or_empty(
 
404
            file_id, self.repository.get_transaction())
 
405
        # Don't change this to add_lines - add_lines_with_ghosts is cheaper
 
406
        # than add_lines, and allows committing when a parent is ghosted for
 
407
        # some reason.
420
408
        # Note: as we read the content directly from the tree, we know its not
421
409
        # been turned into unicode or badly split - but a broken tree
422
410
        # implementation could give us bad output from readlines() so this is
423
411
        # not a guarantee of safety. What would be better is always checking
424
412
        # the content during test suite execution. RBC 20070912
425
 
        parent_keys = tuple((file_id, parent) for parent in parents)
426
 
        return self.repository.texts.add_lines(
427
 
            (file_id, self._new_revision_id), parent_keys, new_lines,
428
 
            nostore_sha=nostore_sha, random_id=self.random_revid,
429
 
            check_content=False)[0:2]
 
413
        try:
 
414
            return versionedfile.add_lines_with_ghosts(
 
415
                self._new_revision_id, parents, new_lines,
 
416
                nostore_sha=nostore_sha, random_id=self.random_revid,
 
417
                check_content=False)[0:2]
 
418
        finally:
 
419
            versionedfile.clear_cache()
430
420
 
431
421
 
432
422
class RootCommitBuilder(CommitBuilder):
455
445
    revisions and file history.  It's normally accessed only by the Branch,
456
446
    which views a particular line of development through that history.
457
447
 
458
 
    The Repository builds on top of some byte storage facilies (the revisions,
459
 
    signatures, inventories and texts attributes) and a Transport, which
460
 
    respectively provide byte storage and a means to access the (possibly
 
448
    The Repository builds on top of Stores and a Transport, which respectively 
 
449
    describe the disk data format and the way of accessing the (possibly 
461
450
    remote) disk.
462
 
 
463
 
    The byte storage facilities are addressed via tuples, which we refer to
464
 
    as 'keys' throughout the code base. Revision_keys, inventory_keys and
465
 
    signature_keys are all 1-tuples: (revision_id,). text_keys are two-tuples:
466
 
    (file_id, revision_id). We use this interface because it allows low
467
 
    friction with the underlying code that implements disk indices, network
468
 
    encoding and other parts of bzrlib.
469
 
 
470
 
    :ivar revisions: A bzrlib.versionedfile.VersionedFiles instance containing
471
 
        the serialised revisions for the repository. This can be used to obtain
472
 
        revision graph information or to access raw serialised revisions.
473
 
        The result of trying to insert data into the repository via this store
474
 
        is undefined: it should be considered read-only except for implementors
475
 
        of repositories.
476
 
    :ivar signatures: A bzrlib.versionedfile.VersionedFiles instance containing
477
 
        the serialised signatures for the repository. This can be used to
478
 
        obtain access to raw serialised signatures.  The result of trying to
479
 
        insert data into the repository via this store is undefined: it should
480
 
        be considered read-only except for implementors of repositories.
481
 
    :ivar inventories: A bzrlib.versionedfile.VersionedFiles instance containing
482
 
        the serialised inventories for the repository. This can be used to
483
 
        obtain unserialised inventories.  The result of trying to insert data
484
 
        into the repository via this store is undefined: it should be
485
 
        considered read-only except for implementors of repositories.
486
 
    :ivar texts: A bzrlib.versionedfile.VersionedFiles instance containing the
487
 
        texts of files and directories for the repository. This can be used to
488
 
        obtain file texts or file graphs. Note that Repository.iter_file_bytes
489
 
        is usually a better interface for accessing file texts.
490
 
        The result of trying to insert data into the repository via this store
491
 
        is undefined: it should be considered read-only except for implementors
492
 
        of repositories.
493
 
    :ivar _transport: Transport for file access to repository, typically
494
 
        pointing to .bzr/repository.
495
451
    """
496
452
 
497
453
    # What class to use for a CommitBuilder. Often its simpler to change this
529
485
        attempted.
530
486
        """
531
487
 
532
 
    def add_fallback_repository(self, repository):
533
 
        """Add a repository to use for looking up data not held locally.
534
 
        
535
 
        :param repository: A repository.
536
 
        """
537
 
        if not self._format.supports_external_lookups:
538
 
            raise errors.UnstackableRepositoryFormat(self._format, self.base)
539
 
        self._check_fallback_repository(repository)
540
 
        self._fallback_repositories.append(repository)
541
 
        self.texts.add_fallback_versioned_files(repository.texts)
542
 
        self.inventories.add_fallback_versioned_files(repository.inventories)
543
 
        self.revisions.add_fallback_versioned_files(repository.revisions)
544
 
        self.signatures.add_fallback_versioned_files(repository.signatures)
545
 
 
546
 
    def _check_fallback_repository(self, repository):
547
 
        """Check that this repository can fallback to repository safely.
548
 
 
549
 
        Raise an error if not.
550
 
        
551
 
        :param repository: A repository to fallback to.
552
 
        """
553
 
        return InterRepository._assert_same_model(self, repository)
554
 
 
 
488
    @needs_write_lock
555
489
    def add_inventory(self, revision_id, inv, parents):
556
490
        """Add the inventory inv to the repository as revision_id.
557
491
        
558
492
        :param parents: The revision ids of the parents that revision_id
559
493
                        is known to have and are in the repository already.
560
494
 
561
 
        :returns: The validator(which is a sha1 digest, though what is sha'd is
562
 
            repository format specific) of the serialized inventory.
 
495
        returns the sha1 of the serialized inventory.
563
496
        """
564
 
        if not self.is_in_write_group():
565
 
            raise AssertionError("%r not in write group" % (self,))
 
497
        assert self.is_in_write_group()
566
498
        _mod_revision.check_not_reserved_id(revision_id)
567
 
        if not (inv.revision_id is None or inv.revision_id == revision_id):
568
 
            raise AssertionError(
569
 
                "Mismatch between inventory revision"
570
 
                " id and insertion revid (%r, %r)"
571
 
                % (inv.revision_id, revision_id))
572
 
        if inv.root is None:
573
 
            raise AssertionError()
 
499
        assert inv.revision_id is None or inv.revision_id == revision_id, \
 
500
            "Mismatch between inventory revision" \
 
501
            " id and insertion revid (%r, %r)" % (inv.revision_id, revision_id)
 
502
        assert inv.root is not None
574
503
        inv_lines = self._serialise_inventory_to_lines(inv)
575
 
        return self._inventory_add_lines(revision_id, parents,
 
504
        inv_vf = self.get_inventory_weave()
 
505
        return self._inventory_add_lines(inv_vf, revision_id, parents,
576
506
            inv_lines, check_content=False)
577
507
 
578
 
    def _inventory_add_lines(self, revision_id, parents, lines,
 
508
    def _inventory_add_lines(self, inv_vf, revision_id, parents, lines,
579
509
        check_content=True):
580
510
        """Store lines in inv_vf and return the sha1 of the inventory."""
581
 
        parents = [(parent,) for parent in parents]
582
 
        return self.inventories.add_lines((revision_id,), parents, lines,
 
511
        final_parents = []
 
512
        for parent in parents:
 
513
            if parent in inv_vf:
 
514
                final_parents.append(parent)
 
515
        return inv_vf.add_lines(revision_id, final_parents, lines,
583
516
            check_content=check_content)[0]
584
517
 
 
518
    @needs_write_lock
585
519
    def add_revision(self, revision_id, rev, inv=None, config=None):
586
520
        """Add rev to the revision store as revision_id.
587
521
 
602
536
            plaintext = Testament(rev, inv).as_short_text()
603
537
            self.store_revision_signature(
604
538
                gpg.GPGStrategy(config), plaintext, revision_id)
605
 
        # check inventory present
606
 
        if not self.inventories.get_parent_map([(revision_id,)]):
 
539
        if not revision_id in self.get_inventory_weave():
607
540
            if inv is None:
608
541
                raise errors.WeaveRevisionNotPresent(revision_id,
609
 
                                                     self.inventories)
 
542
                                                     self.get_inventory_weave())
610
543
            else:
611
544
                # yes, this is not suitable for adding with ghosts.
612
 
                rev.inventory_sha1 = self.add_inventory(revision_id, inv,
613
 
                                                        rev.parent_ids)
614
 
        else:
615
 
            key = (revision_id,)
616
 
            rev.inventory_sha1 = self.inventories.get_sha1s([key])[key]
617
 
        self._add_revision(rev)
 
545
                self.add_inventory(revision_id, inv, rev.parent_ids)
 
546
        self._revision_store.add_revision(rev, self.get_transaction())
618
547
 
619
 
    def _add_revision(self, revision):
620
 
        text = self._serializer.write_revision_to_string(revision)
621
 
        key = (revision.revision_id,)
622
 
        parents = tuple((parent,) for parent in revision.parent_ids)
623
 
        self.revisions.add_lines(key, parents, osutils.split_lines(text))
 
548
    def _add_revision_text(self, revision_id, text):
 
549
        revision = self._revision_store._serializer.read_revision_from_string(
 
550
            text)
 
551
        self._revision_store._add_revision(revision, StringIO(text),
 
552
                                           self.get_transaction())
624
553
 
625
554
    def all_revision_ids(self):
626
555
        """Returns a list of all the revision ids in the repository. 
627
556
 
628
 
        This is conceptually deprecated because code should generally work on
629
 
        the graph reachable from a particular revision, and ignore any other
630
 
        revisions that might be present.  There is no direct replacement
631
 
        method.
 
557
        This is deprecated because code should generally work on the graph
 
558
        reachable from a particular revision, and ignore any other revisions
 
559
        that might be present.  There is no direct replacement method.
632
560
        """
633
561
        if 'evil' in debug.debug_flags:
634
562
            mutter_callsite(2, "all_revision_ids is linear with history.")
657
585
        Returns a set of the present revisions.
658
586
        """
659
587
        result = []
660
 
        graph = self.get_graph()
661
 
        parent_map = graph.get_parent_map(revision_ids)
662
 
        # The old API returned a list, should this actually be a set?
663
 
        return parent_map.keys()
 
588
        for id in revision_ids:
 
589
            if self.has_revision(id):
 
590
               result.append(id)
 
591
        return result
664
592
 
665
593
    @staticmethod
666
594
    def create(a_bzrdir):
667
595
        """Construct the current default format repository in a_bzrdir."""
668
596
        return RepositoryFormat.get_default_format().initialize(a_bzrdir)
669
597
 
670
 
    def __init__(self, _format, a_bzrdir, control_files):
 
598
    def __init__(self, _format, a_bzrdir, control_files, _revision_store, control_store, text_store):
671
599
        """instantiate a Repository.
672
600
 
673
601
        :param _format: The format of the repository on disk.
682
610
        # the following are part of the public API for Repository:
683
611
        self.bzrdir = a_bzrdir
684
612
        self.control_files = control_files
685
 
        self._transport = control_files._transport
686
 
        self.base = self._transport.base
 
613
        self._revision_store = _revision_store
 
614
        # backwards compatibility
 
615
        self.weave_store = text_store
687
616
        # for tests
688
617
        self._reconcile_does_inventory_gc = True
689
618
        self._reconcile_fixes_text_parents = False
690
 
        self._reconcile_backsup_inventory = True
691
619
        # not right yet - should be more semantically clear ? 
692
620
        # 
 
621
        self.control_store = control_store
 
622
        self.control_weaves = control_store
693
623
        # TODO: make sure to construct the right store classes, etc, depending
694
624
        # on whether escaping is required.
695
625
        self._warn_if_deprecated()
696
626
        self._write_group = None
697
 
        # Additional places to query for data.
698
 
        self._fallback_repositories = []
699
 
        # What order should fetch operations request streams in?
700
 
        # The default is unordered as that is the cheapest for an origin to
701
 
        # provide.
702
 
        self._fetch_order = 'unordered'
703
 
        # Does this repository use deltas that can be fetched as-deltas ?
704
 
        # (E.g. knits, where the knit deltas can be transplanted intact.
705
 
        # We default to False, which will ensure that enough data to get
706
 
        # a full text out of any fetch stream will be grabbed.
707
 
        self._fetch_uses_deltas = False
708
 
        # Should fetch trigger a reconcile after the fetch? Only needed for
709
 
        # some repository formats that can suffer internal inconsistencies.
710
 
        self._fetch_reconcile = False
 
627
        self.base = control_files._transport.base
711
628
 
712
629
    def __repr__(self):
713
630
        return '%s(%r)' % (self.__class__.__name__,
722
639
        """
723
640
        if self.__class__ is not other.__class__:
724
641
            return False
725
 
        return (self._transport.base == other._transport.base)
 
642
        return (self.control_files._transport.base ==
 
643
                other.control_files._transport.base)
726
644
 
727
645
    def is_in_write_group(self):
728
646
        """Return True if there is an open write group.
761
679
        XXX: this docstring is duplicated in many places, e.g. lockable_files.py
762
680
        """
763
681
        result = self.control_files.lock_write(token=token)
764
 
        for repo in self._fallback_repositories:
765
 
            # Writes don't affect fallback repos
766
 
            repo.lock_read()
767
682
        self._refresh_data()
768
683
        return result
769
684
 
770
685
    def lock_read(self):
771
686
        self.control_files.lock_read()
772
 
        for repo in self._fallback_repositories:
773
 
            repo.lock_read()
774
687
        self._refresh_data()
775
688
 
776
689
    def get_physical_lock_status(self):
836
749
                last_revision.timezone)
837
750
 
838
751
        # now gather global repository information
839
 
        # XXX: This is available for many repos regardless of listability.
840
752
        if self.bzrdir.root_transport.listable():
841
 
            # XXX: do we want to __define len__() ?
842
 
            # Maybe the versionedfiles object should provide a different
843
 
            # method to get the number of keys.
844
 
            result['revisions'] = len(self.revisions.keys())
845
 
            # result['size'] = t
 
753
            c, t = self._revision_store.total_size(self.get_transaction())
 
754
            result['revisions'] = c
 
755
            result['size'] = t
846
756
        return result
847
757
 
848
 
    def find_branches(self, using=False):
849
 
        """Find branches underneath this repository.
850
 
 
851
 
        This will include branches inside other branches.
852
 
 
853
 
        :param using: If True, list only branches using this repository.
854
 
        """
855
 
        if using and not self.is_shared():
856
 
            try:
857
 
                return [self.bzrdir.open_branch()]
858
 
            except errors.NotBranchError:
859
 
                return []
860
 
        class Evaluator(object):
861
 
 
862
 
            def __init__(self):
863
 
                self.first_call = True
864
 
 
865
 
            def __call__(self, bzrdir):
866
 
                # On the first call, the parameter is always the bzrdir
867
 
                # containing the current repo.
868
 
                if not self.first_call:
869
 
                    try:
870
 
                        repository = bzrdir.open_repository()
871
 
                    except errors.NoRepositoryPresent:
872
 
                        pass
873
 
                    else:
874
 
                        return False, (None, repository)
875
 
                self.first_call = False
876
 
                try:
877
 
                    value = (bzrdir.open_branch(), None)
878
 
                except errors.NotBranchError:
879
 
                    value = (None, None)
880
 
                return True, value
881
 
 
882
 
        branches = []
883
 
        for branch, repository in bzrdir.BzrDir.find_bzrdirs(
884
 
                self.bzrdir.root_transport, evaluate=Evaluator()):
885
 
            if branch is not None:
886
 
                branches.append(branch)
887
 
            if not using and repository is not None:
888
 
                branches.extend(repository.find_branches())
889
 
        return branches
890
 
 
891
 
    @needs_read_lock
892
 
    def search_missing_revision_ids(self, other, revision_id=None, find_ghosts=True):
893
 
        """Return the revision ids that other has that this does not.
894
 
        
895
 
        These are returned in topological order.
896
 
 
897
 
        revision_id: only return revision ids included by revision_id.
898
 
        """
899
 
        return InterRepository.get(other, self).search_missing_revision_ids(
900
 
            revision_id, find_ghosts)
901
 
 
902
 
    @deprecated_method(one_two)
903
 
    @needs_read_lock
904
 
    def missing_revision_ids(self, other, revision_id=None, find_ghosts=True):
905
 
        """Return the revision ids that other has that this does not.
906
 
        
907
 
        These are returned in topological order.
908
 
 
909
 
        revision_id: only return revision ids included by revision_id.
910
 
        """
911
 
        keys =  self.search_missing_revision_ids(
912
 
            other, revision_id, find_ghosts).get_keys()
913
 
        other.lock_read()
914
 
        try:
915
 
            parents = other.get_graph().get_parent_map(keys)
916
 
        finally:
917
 
            other.unlock()
918
 
        return tsort.topo_sort(parents)
 
758
    def get_data_stream(self, revision_ids):
 
759
        raise NotImplementedError(self.get_data_stream)
 
760
 
 
761
    def insert_data_stream(self, stream):
 
762
        """XXX What does this really do? 
 
763
        
 
764
        Is it a substitute for fetch? 
 
765
        Should it manage its own write group ?
 
766
        """
 
767
        for item_key, bytes in stream:
 
768
            if item_key[0] == 'file':
 
769
                (file_id,) = item_key[1:]
 
770
                knit = self.weave_store.get_weave_or_empty(
 
771
                    file_id, self.get_transaction())
 
772
            elif item_key == ('inventory',):
 
773
                knit = self.get_inventory_weave()
 
774
            elif item_key == ('revisions',):
 
775
                knit = self._revision_store.get_revision_file(
 
776
                    self.get_transaction())
 
777
            elif item_key == ('signatures',):
 
778
                knit = self._revision_store.get_signature_file(
 
779
                    self.get_transaction())
 
780
            else:
 
781
                raise RepositoryDataStreamError(
 
782
                    "Unrecognised data stream key '%s'" % (item_key,))
 
783
            decoded_list = bencode.bdecode(bytes)
 
784
            format = decoded_list.pop(0)
 
785
            data_list = []
 
786
            knit_bytes = ''
 
787
            for version, options, parents, some_bytes in decoded_list:
 
788
                data_list.append((version, options, len(some_bytes), parents))
 
789
                knit_bytes += some_bytes
 
790
            knit.insert_data_stream(
 
791
                (format, data_list, StringIO(knit_bytes).read))
 
792
 
 
793
    @needs_read_lock
 
794
    def missing_revision_ids(self, other, revision_id=None):
 
795
        """Return the revision ids that other has that this does not.
 
796
        
 
797
        These are returned in topological order.
 
798
 
 
799
        revision_id: only return revision ids included by revision_id.
 
800
        """
 
801
        return InterRepository.get(other, self).missing_revision_ids(revision_id)
919
802
 
920
803
    @staticmethod
921
804
    def open(base):
973
856
                not _mod_revision.is_null(revision_id)):
974
857
                self.get_revision(revision_id)
975
858
            return 0, []
976
 
        # if there is no specific appropriate InterRepository, this will get
977
 
        # the InterRepository base class, which raises an
978
 
        # IncompatibleRepositories when asked to fetch.
979
859
        inter = InterRepository.get(source, self)
980
 
        return inter.fetch(revision_id=revision_id, pb=pb,
981
 
            find_ghosts=find_ghosts)
 
860
        try:
 
861
            return inter.fetch(revision_id=revision_id, pb=pb, find_ghosts=find_ghosts)
 
862
        except NotImplementedError:
 
863
            raise errors.IncompatibleRepositories(source, self)
982
864
 
983
865
    def create_bundle(self, target, base, fileobj, format=None):
984
866
        return serializer.write_bundle(self, target, base, fileobj, format)
1011
893
                raise errors.BzrError(
1012
894
                    'Must end write groups before releasing write locks.')
1013
895
        self.control_files.unlock()
1014
 
        for repo in self._fallback_repositories:
1015
 
            repo.unlock()
1016
896
 
1017
897
    @needs_read_lock
1018
898
    def clone(self, a_bzrdir, revision_id=None):
1084
964
    @needs_read_lock
1085
965
    def has_revision(self, revision_id):
1086
966
        """True if this repository has a copy of the revision."""
1087
 
        return revision_id in self.has_revisions((revision_id,))
1088
 
 
1089
 
    @needs_read_lock
1090
 
    def has_revisions(self, revision_ids):
1091
 
        """Probe to find out the presence of multiple revisions.
1092
 
 
1093
 
        :param revision_ids: An iterable of revision_ids.
1094
 
        :return: A set of the revision_ids that were present.
1095
 
        """
1096
 
        parent_map = self.revisions.get_parent_map(
1097
 
            [(rev_id,) for rev_id in revision_ids])
1098
 
        result = set()
1099
 
        if _mod_revision.NULL_REVISION in revision_ids:
1100
 
            result.add(_mod_revision.NULL_REVISION)
1101
 
        result.update([key[0] for key in parent_map])
1102
 
        return result
 
967
        if 'evil' in debug.debug_flags:
 
968
            mutter_callsite(3, "has_revision is a LBYL symptom.")
 
969
        return self._revision_store.has_revision_id(revision_id,
 
970
                                                    self.get_transaction())
1103
971
 
1104
972
    @needs_read_lock
1105
973
    def get_revision(self, revision_id):
1128
996
        for rev_id in revision_ids:
1129
997
            if not rev_id or not isinstance(rev_id, basestring):
1130
998
                raise errors.InvalidRevisionId(revision_id=rev_id, branch=self)
1131
 
        keys = [(key,) for key in revision_ids]
1132
 
        stream = self.revisions.get_record_stream(keys, 'unordered', True)
1133
 
        revs = {}
1134
 
        for record in stream:
1135
 
            if record.storage_kind == 'absent':
1136
 
                raise errors.NoSuchRevision(self, record.key[0])
1137
 
            text = record.get_bytes_as('fulltext')
1138
 
            rev = self._serializer.read_revision_from_string(text)
1139
 
            revs[record.key[0]] = rev
1140
 
        return [revs[revid] for revid in revision_ids]
 
999
        revs = self._revision_store.get_revisions(revision_ids,
 
1000
                                                  self.get_transaction())
 
1001
        for rev in revs:
 
1002
            assert not isinstance(rev.revision_id, unicode)
 
1003
            for parent_id in rev.parent_ids:
 
1004
                assert not isinstance(parent_id, unicode)
 
1005
        return revs
1141
1006
 
1142
1007
    @needs_read_lock
1143
1008
    def get_revision_xml(self, revision_id):
1147
1012
        rev = self.get_revision(revision_id)
1148
1013
        rev_tmp = StringIO()
1149
1014
        # the current serializer..
1150
 
        self._serializer.write_revision(rev, rev_tmp)
 
1015
        self._revision_store._serializer.write_revision(rev, rev_tmp)
1151
1016
        rev_tmp.seek(0)
1152
1017
        return rev_tmp.getvalue()
1153
1018
 
 
1019
    @needs_read_lock
1154
1020
    def get_deltas_for_revisions(self, revisions):
1155
1021
        """Produce a generator of revision deltas.
1156
1022
        
1184
1050
    @needs_write_lock
1185
1051
    def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
1186
1052
        signature = gpg_strategy.sign(plaintext)
1187
 
        self.add_signature_text(revision_id, signature)
1188
 
 
1189
 
    @needs_write_lock
1190
 
    def add_signature_text(self, revision_id, signature):
1191
 
        self.signatures.add_lines((revision_id,), (),
1192
 
            osutils.split_lines(signature))
1193
 
 
1194
 
    def find_text_key_references(self):
1195
 
        """Find the text key references within the repository.
1196
 
 
1197
 
        :return: a dictionary mapping (file_id, revision_id) tuples to altered file-ids to an iterable of
 
1053
        self._revision_store.add_revision_signature_text(revision_id,
 
1054
                                                         signature,
 
1055
                                                         self.get_transaction())
 
1056
 
 
1057
    def _find_file_ids_from_xml_inventory_lines(self, line_iterator,
 
1058
        revision_ids):
 
1059
        """Helper routine for fileids_altered_by_revision_ids.
 
1060
 
 
1061
        This performs the translation of xml lines to revision ids.
 
1062
 
 
1063
        :param line_iterator: An iterator of lines
 
1064
        :param revision_ids: The revision ids to filter for. This should be a
 
1065
            set or other type which supports efficient __contains__ lookups, as
 
1066
            the revision id from each parsed line will be looked up in the
 
1067
            revision_ids filter.
 
1068
        :return: a dictionary mapping altered file-ids to an iterable of
1198
1069
        revision_ids. Each altered file-ids has the exact revision_ids that
1199
1070
        altered it listed explicitly.
1200
 
        :return: A dictionary mapping text keys ((fileid, revision_id) tuples)
1201
 
            to whether they were referred to by the inventory of the
1202
 
            revision_id that they contain. The inventory texts from all present
1203
 
            revision ids are assessed to generate this report.
1204
 
        """
1205
 
        revision_keys = self.revisions.keys()
1206
 
        w = self.inventories
1207
 
        pb = ui.ui_factory.nested_progress_bar()
1208
 
        try:
1209
 
            return self._find_text_key_references_from_xml_inventory_lines(
1210
 
                w.iter_lines_added_or_present_in_keys(revision_keys, pb=pb))
1211
 
        finally:
1212
 
            pb.finished()
1213
 
 
1214
 
    def _find_text_key_references_from_xml_inventory_lines(self,
1215
 
        line_iterator):
1216
 
        """Core routine for extracting references to texts from inventories.
1217
 
 
1218
 
        This performs the translation of xml lines to revision ids.
1219
 
 
1220
 
        :param line_iterator: An iterator of lines, origin_version_id
1221
 
        :return: A dictionary mapping text keys ((fileid, revision_id) tuples)
1222
 
            to whether they were referred to by the inventory of the
1223
 
            revision_id that they contain. Note that if that revision_id was
1224
 
            not part of the line_iterator's output then False will be given -
1225
 
            even though it may actually refer to that key.
1226
 
        """
1227
 
        if not self._serializer.support_altered_by_hack:
1228
 
            raise AssertionError(
1229
 
                "_find_text_key_references_from_xml_inventory_lines only "
1230
 
                "supported for branches which store inventory as unnested xml"
1231
 
                ", not on %r" % self)
 
1071
        """
1232
1072
        result = {}
1233
1073
 
1234
1074
        # this code needs to read every new line in every inventory for the
1250
1090
        search = self._file_ids_altered_regex.search
1251
1091
        unescape = _unescape_xml
1252
1092
        setdefault = result.setdefault
1253
 
        for line, line_key in line_iterator:
 
1093
        for line in line_iterator:
1254
1094
            match = search(line)
1255
1095
            if match is None:
1256
1096
                continue
1274
1114
                unescape_revid_cache[revision_id] = unescaped
1275
1115
                revision_id = unescaped
1276
1116
 
1277
 
            # Note that unconditionally unescaping means that we deserialise
1278
 
            # every fileid, which for general 'pull' is not great, but we don't
1279
 
            # really want to have some many fulltexts that this matters anyway.
1280
 
            # RBC 20071114.
1281
 
            try:
1282
 
                file_id = unescape_fileid_cache[file_id]
1283
 
            except KeyError:
1284
 
                unescaped = unescape(file_id)
1285
 
                unescape_fileid_cache[file_id] = unescaped
1286
 
                file_id = unescaped
1287
 
 
1288
 
            key = (file_id, revision_id)
1289
 
            setdefault(key, False)
1290
 
            if revision_id == line_key[-1]:
1291
 
                result[key] = True
1292
 
        return result
1293
 
 
1294
 
    def _find_file_ids_from_xml_inventory_lines(self, line_iterator,
1295
 
        revision_ids):
1296
 
        """Helper routine for fileids_altered_by_revision_ids.
1297
 
 
1298
 
        This performs the translation of xml lines to revision ids.
1299
 
 
1300
 
        :param line_iterator: An iterator of lines, origin_version_id
1301
 
        :param revision_ids: The revision ids to filter for. This should be a
1302
 
            set or other type which supports efficient __contains__ lookups, as
1303
 
            the revision id from each parsed line will be looked up in the
1304
 
            revision_ids filter.
1305
 
        :return: a dictionary mapping altered file-ids to an iterable of
1306
 
        revision_ids. Each altered file-ids has the exact revision_ids that
1307
 
        altered it listed explicitly.
1308
 
        """
1309
 
        result = {}
1310
 
        setdefault = result.setdefault
1311
 
        for key in \
1312
 
            self._find_text_key_references_from_xml_inventory_lines(
1313
 
                line_iterator).iterkeys():
1314
 
            # once data is all ensured-consistent; then this is
1315
 
            # if revision_id == version_id
1316
 
            if key[-1:] in revision_ids:
1317
 
                setdefault(key[0], set()).add(key[-1])
1318
 
        return result
1319
 
 
1320
 
    def fileids_altered_by_revision_ids(self, revision_ids, _inv_weave=None):
 
1117
            if revision_id in revision_ids:
 
1118
                try:
 
1119
                    file_id = unescape_fileid_cache[file_id]
 
1120
                except KeyError:
 
1121
                    unescaped = unescape(file_id)
 
1122
                    unescape_fileid_cache[file_id] = unescaped
 
1123
                    file_id = unescaped
 
1124
                setdefault(file_id, set()).add(revision_id)
 
1125
        return result
 
1126
 
 
1127
    def fileids_altered_by_revision_ids(self, revision_ids):
1321
1128
        """Find the file ids and versions affected by revisions.
1322
1129
 
1323
1130
        :param revisions: an iterable containing revision ids.
1324
 
        :param _inv_weave: The inventory weave from this repository or None.
1325
 
            If None, the inventory weave will be opened automatically.
1326
1131
        :return: a dictionary mapping altered file-ids to an iterable of
1327
1132
        revision_ids. Each altered file-ids has the exact revision_ids that
1328
1133
        altered it listed explicitly.
1329
1134
        """
1330
 
        selected_keys = set((revid,) for revid in revision_ids)
1331
 
        w = _inv_weave or self.inventories
 
1135
        assert self._serializer.support_altered_by_hack, \
 
1136
            ("fileids_altered_by_revision_ids only supported for branches " 
 
1137
             "which store inventory as unnested xml, not on %r" % self)
 
1138
        selected_revision_ids = set(revision_ids)
 
1139
        w = self.get_inventory_weave()
1332
1140
        pb = ui.ui_factory.nested_progress_bar()
1333
1141
        try:
1334
1142
            return self._find_file_ids_from_xml_inventory_lines(
1335
 
                w.iter_lines_added_or_present_in_keys(
1336
 
                    selected_keys, pb=pb),
1337
 
                selected_keys)
 
1143
                w.iter_lines_added_or_present_in_versions(
 
1144
                    selected_revision_ids, pb=pb),
 
1145
                selected_revision_ids)
1338
1146
        finally:
1339
1147
            pb.finished()
1340
1148
 
1351
1159
 
1352
1160
        bytes_iterator is an iterable of bytestrings for the file.  The
1353
1161
        kind of iterable and length of the bytestrings are unspecified, but for
1354
 
        this implementation, it is a list of bytes produced by
1355
 
        VersionedFile.get_record_stream().
 
1162
        this implementation, it is a list of lines produced by
 
1163
        VersionedFile.get_lines().
1356
1164
 
1357
1165
        :param desired_files: a list of (file_id, revision_id, identifier)
1358
1166
            triples
1359
1167
        """
1360
1168
        transaction = self.get_transaction()
1361
 
        text_keys = {}
1362
1169
        for file_id, revision_id, callable_data in desired_files:
1363
 
            text_keys[(file_id, revision_id)] = callable_data
1364
 
        for record in self.texts.get_record_stream(text_keys, 'unordered', True):
1365
 
            if record.storage_kind == 'absent':
1366
 
                raise errors.RevisionNotPresent(record.key, self)
1367
 
            yield text_keys[record.key], record.get_bytes_as('fulltext')
1368
 
 
1369
 
    def _generate_text_key_index(self, text_key_references=None,
1370
 
        ancestors=None):
1371
 
        """Generate a new text key index for the repository.
1372
 
 
1373
 
        This is an expensive function that will take considerable time to run.
1374
 
 
1375
 
        :return: A dict mapping text keys ((file_id, revision_id) tuples) to a
1376
 
            list of parents, also text keys. When a given key has no parents,
1377
 
            the parents list will be [NULL_REVISION].
1378
 
        """
1379
 
        # All revisions, to find inventory parents.
1380
 
        if ancestors is None:
1381
 
            graph = self.get_graph()
1382
 
            ancestors = graph.get_parent_map(self.all_revision_ids())
1383
 
        if text_key_references is None:
1384
 
            text_key_references = self.find_text_key_references()
1385
 
        pb = ui.ui_factory.nested_progress_bar()
1386
 
        try:
1387
 
            return self._do_generate_text_key_index(ancestors,
1388
 
                text_key_references, pb)
1389
 
        finally:
1390
 
            pb.finished()
1391
 
 
1392
 
    def _do_generate_text_key_index(self, ancestors, text_key_references, pb):
1393
 
        """Helper for _generate_text_key_index to avoid deep nesting."""
1394
 
        revision_order = tsort.topo_sort(ancestors)
1395
 
        invalid_keys = set()
1396
 
        revision_keys = {}
1397
 
        for revision_id in revision_order:
1398
 
            revision_keys[revision_id] = set()
1399
 
        text_count = len(text_key_references)
1400
 
        # a cache of the text keys to allow reuse; costs a dict of all the
1401
 
        # keys, but saves a 2-tuple for every child of a given key.
1402
 
        text_key_cache = {}
1403
 
        for text_key, valid in text_key_references.iteritems():
1404
 
            if not valid:
1405
 
                invalid_keys.add(text_key)
1406
 
            else:
1407
 
                revision_keys[text_key[1]].add(text_key)
1408
 
            text_key_cache[text_key] = text_key
1409
 
        del text_key_references
1410
 
        text_index = {}
1411
 
        text_graph = graph.Graph(graph.DictParentsProvider(text_index))
1412
 
        NULL_REVISION = _mod_revision.NULL_REVISION
1413
 
        # Set a cache with a size of 10 - this suffices for bzr.dev but may be
1414
 
        # too small for large or very branchy trees. However, for 55K path
1415
 
        # trees, it would be easy to use too much memory trivially. Ideally we
1416
 
        # could gauge this by looking at available real memory etc, but this is
1417
 
        # always a tricky proposition.
1418
 
        inventory_cache = lru_cache.LRUCache(10)
1419
 
        batch_size = 10 # should be ~150MB on a 55K path tree
1420
 
        batch_count = len(revision_order) / batch_size + 1
1421
 
        processed_texts = 0
1422
 
        pb.update("Calculating text parents.", processed_texts, text_count)
1423
 
        for offset in xrange(batch_count):
1424
 
            to_query = revision_order[offset * batch_size:(offset + 1) *
1425
 
                batch_size]
1426
 
            if not to_query:
1427
 
                break
1428
 
            for rev_tree in self.revision_trees(to_query):
1429
 
                revision_id = rev_tree.get_revision_id()
1430
 
                parent_ids = ancestors[revision_id]
1431
 
                for text_key in revision_keys[revision_id]:
1432
 
                    pb.update("Calculating text parents.", processed_texts)
1433
 
                    processed_texts += 1
1434
 
                    candidate_parents = []
1435
 
                    for parent_id in parent_ids:
1436
 
                        parent_text_key = (text_key[0], parent_id)
1437
 
                        try:
1438
 
                            check_parent = parent_text_key not in \
1439
 
                                revision_keys[parent_id]
1440
 
                        except KeyError:
1441
 
                            # the parent parent_id is a ghost:
1442
 
                            check_parent = False
1443
 
                            # truncate the derived graph against this ghost.
1444
 
                            parent_text_key = None
1445
 
                        if check_parent:
1446
 
                            # look at the parent commit details inventories to
1447
 
                            # determine possible candidates in the per file graph.
1448
 
                            # TODO: cache here.
1449
 
                            try:
1450
 
                                inv = inventory_cache[parent_id]
1451
 
                            except KeyError:
1452
 
                                inv = self.revision_tree(parent_id).inventory
1453
 
                                inventory_cache[parent_id] = inv
1454
 
                            parent_entry = inv._byid.get(text_key[0], None)
1455
 
                            if parent_entry is not None:
1456
 
                                parent_text_key = (
1457
 
                                    text_key[0], parent_entry.revision)
1458
 
                            else:
1459
 
                                parent_text_key = None
1460
 
                        if parent_text_key is not None:
1461
 
                            candidate_parents.append(
1462
 
                                text_key_cache[parent_text_key])
1463
 
                    parent_heads = text_graph.heads(candidate_parents)
1464
 
                    new_parents = list(parent_heads)
1465
 
                    new_parents.sort(key=lambda x:candidate_parents.index(x))
1466
 
                    if new_parents == []:
1467
 
                        new_parents = [NULL_REVISION]
1468
 
                    text_index[text_key] = new_parents
1469
 
 
1470
 
        for text_key in invalid_keys:
1471
 
            text_index[text_key] = [NULL_REVISION]
1472
 
        return text_index
 
1170
            try:
 
1171
                weave = self.weave_store.get_weave(file_id, transaction)
 
1172
            except errors.NoSuchFile:
 
1173
                raise errors.NoSuchIdInRepository(self, file_id)
 
1174
            yield callable_data, weave.get_lines(revision_id)
1473
1175
 
1474
1176
    def item_keys_introduced_by(self, revision_ids, _files_pb=None):
1475
1177
        """Get an iterable listing the keys of all the data introduced by a set
1487
1189
        # maybe this generator should explicitly have the contract that it
1488
1190
        # should not be iterated until the previously yielded item has been
1489
1191
        # processed?
1490
 
        inv_w = self.inventories
 
1192
        self.lock_read()
 
1193
        inv_w = self.get_inventory_weave()
 
1194
        inv_w.enable_cache()
1491
1195
 
1492
1196
        # file ids that changed
1493
 
        file_ids = self.fileids_altered_by_revision_ids(revision_ids, inv_w)
 
1197
        file_ids = self.fileids_altered_by_revision_ids(revision_ids)
1494
1198
        count = 0
1495
1199
        num_file_ids = len(file_ids)
1496
1200
        for file_id, altered_versions in file_ids.iteritems():
1504
1208
 
1505
1209
        # inventory
1506
1210
        yield ("inventory", None, revision_ids)
 
1211
        inv_w.clear_cache()
1507
1212
 
1508
1213
        # signatures
1509
1214
        revisions_with_signatures = set()
1515
1220
                pass
1516
1221
            else:
1517
1222
                revisions_with_signatures.add(rev_id)
 
1223
        self.unlock()
1518
1224
        yield ("signatures", None, revisions_with_signatures)
1519
1225
 
1520
1226
        # revisions
1521
1227
        yield ("revisions", None, revision_ids)
1522
1228
 
1523
1229
    @needs_read_lock
 
1230
    def get_inventory_weave(self):
 
1231
        return self.control_weaves.get_weave('inventory',
 
1232
            self.get_transaction())
 
1233
 
 
1234
    @needs_read_lock
1524
1235
    def get_inventory(self, revision_id):
1525
 
        """Get Inventory object by revision id."""
1526
 
        return self.iter_inventories([revision_id]).next()
1527
 
 
1528
 
    def iter_inventories(self, revision_ids):
1529
 
        """Get many inventories by revision_ids.
1530
 
 
1531
 
        This will buffer some or all of the texts used in constructing the
1532
 
        inventories in memory, but will only parse a single inventory at a
1533
 
        time.
1534
 
 
1535
 
        :return: An iterator of inventories.
1536
 
        """
1537
 
        if ((None in revision_ids)
1538
 
            or (_mod_revision.NULL_REVISION in revision_ids)):
1539
 
            raise ValueError('cannot get null revision inventory')
1540
 
        return self._iter_inventories(revision_ids)
1541
 
 
1542
 
    def _iter_inventories(self, revision_ids):
1543
 
        """single-document based inventory iteration."""
1544
 
        for text, revision_id in self._iter_inventory_xmls(revision_ids):
1545
 
            yield self.deserialise_inventory(revision_id, text)
1546
 
 
1547
 
    def _iter_inventory_xmls(self, revision_ids):
1548
 
        keys = [(revision_id,) for revision_id in revision_ids]
1549
 
        stream = self.inventories.get_record_stream(keys, 'unordered', True)
1550
 
        texts = {}
1551
 
        for record in stream:
1552
 
            if record.storage_kind != 'absent':
1553
 
                texts[record.key] = record.get_bytes_as('fulltext')
1554
 
            else:
1555
 
                raise errors.NoSuchRevision(self, record.key)
1556
 
        for key in keys:
1557
 
            yield texts[key], key[-1]
 
1236
        """Get Inventory object by hash."""
 
1237
        return self.deserialise_inventory(
 
1238
            revision_id, self.get_inventory_xml(revision_id))
1558
1239
 
1559
1240
    def deserialise_inventory(self, revision_id, xml):
1560
1241
        """Transform the xml into an inventory object. 
1562
1243
        :param revision_id: The expected revision id of the inventory.
1563
1244
        :param xml: A serialised inventory.
1564
1245
        """
1565
 
        result = self._serializer.read_inventory_from_string(xml, revision_id)
1566
 
        if result.revision_id != revision_id:
1567
 
            raise AssertionError('revision id mismatch %s != %s' % (
1568
 
                result.revision_id, revision_id))
1569
 
        return result
 
1246
        return self._serializer.read_inventory_from_string(xml, revision_id)
1570
1247
 
1571
1248
    def serialise_inventory(self, inv):
1572
1249
        return self._serializer.write_inventory_to_string(inv)
1580
1257
    @needs_read_lock
1581
1258
    def get_inventory_xml(self, revision_id):
1582
1259
        """Get inventory XML as a file object."""
1583
 
        texts = self._iter_inventory_xmls([revision_id])
1584
1260
        try:
1585
 
            text, revision_id = texts.next()
1586
 
        except StopIteration:
 
1261
            assert isinstance(revision_id, str), type(revision_id)
 
1262
            iw = self.get_inventory_weave()
 
1263
            return iw.get_text(revision_id)
 
1264
        except IndexError:
1587
1265
            raise errors.HistoryMissing(self, 'inventory', revision_id)
1588
 
        return text
1589
1266
 
1590
1267
    @needs_read_lock
1591
1268
    def get_inventory_sha1(self, revision_id):
1593
1270
        """
1594
1271
        return self.get_revision(revision_id).inventory_sha1
1595
1272
 
 
1273
    @needs_read_lock
 
1274
    def get_revision_graph(self, revision_id=None):
 
1275
        """Return a dictionary containing the revision graph.
 
1276
 
 
1277
        NB: This method should not be used as it accesses the entire graph all
 
1278
        at once, which is much more data than most operations should require.
 
1279
 
 
1280
        :param revision_id: The revision_id to get a graph from. If None, then
 
1281
        the entire revision graph is returned. This is a deprecated mode of
 
1282
        operation and will be removed in the future.
 
1283
        :return: a dictionary of revision_id->revision_parents_list.
 
1284
        """
 
1285
        raise NotImplementedError(self.get_revision_graph)
 
1286
 
 
1287
    @needs_read_lock
 
1288
    def get_revision_graph_with_ghosts(self, revision_ids=None):
 
1289
        """Return a graph of the revisions with ghosts marked as applicable.
 
1290
 
 
1291
        :param revision_ids: an iterable of revisions to graph or None for all.
 
1292
        :return: a Graph object with the graph reachable from revision_ids.
 
1293
        """
 
1294
        if 'evil' in debug.debug_flags:
 
1295
            mutter_callsite(3,
 
1296
                "get_revision_graph_with_ghosts scales with size of history.")
 
1297
        result = deprecated_graph.Graph()
 
1298
        if not revision_ids:
 
1299
            pending = set(self.all_revision_ids())
 
1300
            required = set([])
 
1301
        else:
 
1302
            pending = set(revision_ids)
 
1303
            # special case NULL_REVISION
 
1304
            if _mod_revision.NULL_REVISION in pending:
 
1305
                pending.remove(_mod_revision.NULL_REVISION)
 
1306
            required = set(pending)
 
1307
        done = set([])
 
1308
        while len(pending):
 
1309
            revision_id = pending.pop()
 
1310
            try:
 
1311
                rev = self.get_revision(revision_id)
 
1312
            except errors.NoSuchRevision:
 
1313
                if revision_id in required:
 
1314
                    raise
 
1315
                # a ghost
 
1316
                result.add_ghost(revision_id)
 
1317
                continue
 
1318
            for parent_id in rev.parent_ids:
 
1319
                # is this queued or done ?
 
1320
                if (parent_id not in pending and
 
1321
                    parent_id not in done):
 
1322
                    # no, queue it.
 
1323
                    pending.add(parent_id)
 
1324
            result.add_node(revision_id, rev.parent_ids)
 
1325
            done.add(revision_id)
 
1326
        return result
 
1327
 
 
1328
    def _get_history_vf(self):
 
1329
        """Get a versionedfile whose history graph reflects all revisions.
 
1330
 
 
1331
        For weave repositories, this is the inventory weave.
 
1332
        """
 
1333
        return self.get_inventory_weave()
 
1334
 
1596
1335
    def iter_reverse_revision_history(self, revision_id):
1597
1336
        """Iterate backwards through revision ids in the lefthand history
1598
1337
 
1599
1338
        :param revision_id: The revision id to start with.  All its lefthand
1600
1339
            ancestors will be traversed.
1601
1340
        """
1602
 
        graph = self.get_graph()
 
1341
        if revision_id in (None, _mod_revision.NULL_REVISION):
 
1342
            return
1603
1343
        next_id = revision_id
 
1344
        versionedfile = self._get_history_vf()
1604
1345
        while True:
1605
 
            if next_id in (None, _mod_revision.NULL_REVISION):
1606
 
                return
1607
1346
            yield next_id
1608
 
            # Note: The following line may raise KeyError in the event of
1609
 
            # truncated history. We decided not to have a try:except:raise
1610
 
            # RevisionNotPresent here until we see a use for it, because of the
1611
 
            # cost in an inner loop that is by its very nature O(history).
1612
 
            # Robert Collins 20080326
1613
 
            parents = graph.get_parent_map([next_id])[next_id]
 
1347
            parents = versionedfile.get_parents(next_id)
1614
1348
            if len(parents) == 0:
1615
1349
                return
1616
1350
            else:
1632
1366
        else:
1633
1367
            return self.get_inventory(revision_id)
1634
1368
 
 
1369
    @needs_read_lock
1635
1370
    def is_shared(self):
1636
1371
        """Return True if this repository is flagged as a shared repository."""
1637
1372
        raise NotImplementedError(self.is_shared)
1669
1404
            inv = self.get_revision_inventory(revision_id)
1670
1405
            return RevisionTree(self, inv, revision_id)
1671
1406
 
 
1407
    @needs_read_lock
1672
1408
    def revision_trees(self, revision_ids):
1673
1409
        """Return Tree for a revision on this branch.
1674
1410
 
1675
1411
        `revision_id` may not be None or 'null:'"""
1676
 
        inventories = self.iter_inventories(revision_ids)
1677
 
        for inv in inventories:
1678
 
            yield RevisionTree(self, inv, inv.revision_id)
 
1412
        assert None not in revision_ids
 
1413
        assert _mod_revision.NULL_REVISION not in revision_ids
 
1414
        texts = self.get_inventory_weave().get_texts(revision_ids)
 
1415
        for text, revision_id in zip(texts, revision_ids):
 
1416
            inv = self.deserialise_inventory(revision_id, text)
 
1417
            yield RevisionTree(self, inv, revision_id)
1679
1418
 
1680
1419
    @needs_read_lock
1681
1420
    def get_ancestry(self, revision_id, topo_sorted=True):
1691
1430
            return [None]
1692
1431
        if not self.has_revision(revision_id):
1693
1432
            raise errors.NoSuchRevision(self, revision_id)
1694
 
        graph = self.get_graph()
1695
 
        keys = set()
1696
 
        search = graph._make_breadth_first_searcher([revision_id])
1697
 
        while True:
1698
 
            try:
1699
 
                found, ghosts = search.next_with_ghosts()
1700
 
            except StopIteration:
1701
 
                break
1702
 
            keys.update(found)
1703
 
        if _mod_revision.NULL_REVISION in keys:
1704
 
            keys.remove(_mod_revision.NULL_REVISION)
1705
 
        if topo_sorted:
1706
 
            parent_map = graph.get_parent_map(keys)
1707
 
            keys = tsort.topo_sort(parent_map)
1708
 
        return [None] + list(keys)
 
1433
        w = self.get_inventory_weave()
 
1434
        candidates = w.get_ancestry(revision_id, topo_sorted)
 
1435
        return [None] + candidates # self._eliminate_revisions_not_present(candidates)
1709
1436
 
1710
1437
    def pack(self):
1711
1438
        """Compress the data within the repository.
1719
1446
        """
1720
1447
 
1721
1448
    @needs_read_lock
1722
 
    @deprecated_method(one_six)
1723
1449
    def print_file(self, file, revision_id):
1724
1450
        """Print `file` to stdout.
1725
1451
        
1740
1466
    def get_transaction(self):
1741
1467
        return self.control_files.get_transaction()
1742
1468
 
1743
 
    @deprecated_method(one_one)
 
1469
    def revision_parents(self, revision_id):
 
1470
        return self.get_inventory_weave().parent_names(revision_id)
 
1471
 
1744
1472
    def get_parents(self, revision_ids):
1745
1473
        """See StackedParentsProvider.get_parents"""
1746
 
        parent_map = self.get_parent_map(revision_ids)
1747
 
        return [parent_map.get(r, None) for r in revision_ids]
1748
 
 
1749
 
    def get_parent_map(self, revision_ids):
1750
 
        """See graph._StackedParentsProvider.get_parent_map"""
1751
 
        # revisions index works in keys; this just works in revisions
1752
 
        # therefore wrap and unwrap
1753
 
        query_keys = []
1754
 
        result = {}
 
1474
        parents_list = []
1755
1475
        for revision_id in revision_ids:
1756
1476
            if revision_id == _mod_revision.NULL_REVISION:
1757
 
                result[revision_id] = ()
1758
 
            elif revision_id is None:
1759
 
                raise ValueError('get_parent_map(None) is not valid')
1760
 
            else:
1761
 
                query_keys.append((revision_id ,))
1762
 
        for ((revision_id,), parent_keys) in \
1763
 
                self.revisions.get_parent_map(query_keys).iteritems():
1764
 
            if parent_keys:
1765
 
                result[revision_id] = tuple(parent_revid
1766
 
                    for (parent_revid,) in parent_keys)
1767
 
            else:
1768
 
                result[revision_id] = (_mod_revision.NULL_REVISION,)
1769
 
        return result
 
1477
                parents = []
 
1478
            else:
 
1479
                try:
 
1480
                    parents = self.get_revision(revision_id).parent_ids
 
1481
                except errors.NoSuchRevision:
 
1482
                    parents = None
 
1483
                else:
 
1484
                    if len(parents) == 0:
 
1485
                        parents = [_mod_revision.NULL_REVISION]
 
1486
            parents_list.append(parents)
 
1487
        return parents_list
1770
1488
 
1771
1489
    def _make_parents_provider(self):
1772
1490
        return self
1775
1493
        """Return the graph walker for this repository format"""
1776
1494
        parents_provider = self._make_parents_provider()
1777
1495
        if (other_repository is not None and
1778
 
            not self.has_same_location(other_repository)):
 
1496
            other_repository.bzrdir.transport.base !=
 
1497
            self.bzrdir.transport.base):
1779
1498
            parents_provider = graph._StackedParentsProvider(
1780
1499
                [parents_provider, other_repository._make_parents_provider()])
1781
1500
        return graph.Graph(parents_provider)
1782
1501
 
1783
 
    def _get_versioned_file_checker(self):
1784
 
        """Return an object suitable for checking versioned files."""
1785
 
        return _VersionedFileChecker(self)
1786
 
 
1787
 
    def revision_ids_to_search_result(self, result_set):
1788
 
        """Convert a set of revision ids to a graph SearchResult."""
1789
 
        result_parents = set()
1790
 
        for parents in self.get_graph().get_parent_map(
1791
 
            result_set).itervalues():
1792
 
            result_parents.update(parents)
1793
 
        included_keys = result_set.intersection(result_parents)
1794
 
        start_keys = result_set.difference(included_keys)
1795
 
        exclude_keys = result_parents.difference(result_set)
1796
 
        result = graph.SearchResult(start_keys, exclude_keys,
1797
 
            len(result_set), result_set)
1798
 
        return result
 
1502
    def get_versioned_file_checker(self, revisions, revision_versions_cache):
 
1503
        return VersionedFileChecker(revisions, revision_versions_cache, self)
1799
1504
 
1800
1505
    @needs_write_lock
1801
1506
    def set_make_working_trees(self, new_value):
1821
1526
    @needs_read_lock
1822
1527
    def has_signature_for_revision_id(self, revision_id):
1823
1528
        """Query for a revision signature for revision_id in the repository."""
1824
 
        if not self.has_revision(revision_id):
1825
 
            raise errors.NoSuchRevision(self, revision_id)
1826
 
        sig_present = (1 == len(
1827
 
            self.signatures.get_parent_map([(revision_id,)])))
1828
 
        return sig_present
 
1529
        return self._revision_store.has_signature(revision_id,
 
1530
                                                  self.get_transaction())
1829
1531
 
1830
1532
    @needs_read_lock
1831
1533
    def get_signature_text(self, revision_id):
1832
1534
        """Return the text for a signature."""
1833
 
        stream = self.signatures.get_record_stream([(revision_id,)],
1834
 
            'unordered', True)
1835
 
        record = stream.next()
1836
 
        if record.storage_kind == 'absent':
1837
 
            raise errors.NoSuchRevision(self, revision_id)
1838
 
        return record.get_bytes_as('fulltext')
 
1535
        return self._revision_store.get_signature_text(revision_id,
 
1536
                                                       self.get_transaction())
1839
1537
 
1840
1538
    @needs_read_lock
1841
1539
    def check(self, revision_ids=None):
1890
1588
        depend on the revision index being consistent.
1891
1589
        """
1892
1590
        raise NotImplementedError(self.revision_graph_can_have_wrong_parents)
1893
 
 
1894
 
 
 
1591
        
1895
1592
# remove these delegates a while after bzr 0.15
1896
1593
def __make_delegated(name, from_module):
1897
1594
    def _deprecated_repository_forwarder():
1928
1625
 
1929
1626
def install_revision(repository, rev, revision_tree):
1930
1627
    """Install all revision data into a repository."""
1931
 
    install_revisions(repository, [(rev, revision_tree, None)])
1932
 
 
1933
 
 
1934
 
def install_revisions(repository, iterable, num_revisions=None, pb=None):
1935
 
    """Install all revision data into a repository.
1936
 
 
1937
 
    Accepts an iterable of revision, tree, signature tuples.  The signature
1938
 
    may be None.
1939
 
    """
1940
1628
    repository.start_write_group()
1941
1629
    try:
1942
 
        for n, (revision, revision_tree, signature) in enumerate(iterable):
1943
 
            _install_revision(repository, revision, revision_tree, signature)
1944
 
            if pb is not None:
1945
 
                pb.update('Transferring revisions', n + 1, num_revisions)
 
1630
        _install_revision(repository, rev, revision_tree)
1946
1631
    except:
1947
1632
        repository.abort_write_group()
1948
1633
        raise
1950
1635
        repository.commit_write_group()
1951
1636
 
1952
1637
 
1953
 
def _install_revision(repository, rev, revision_tree, signature):
 
1638
def _install_revision(repository, rev, revision_tree):
1954
1639
    """Install all revision data into a repository."""
1955
1640
    present_parents = []
1956
1641
    parent_trees = {}
1968
1653
        path, root = entries.next()
1969
1654
        if root.revision != rev.revision_id:
1970
1655
            raise errors.IncompatibleRevision(repr(repository))
1971
 
    text_keys = {}
 
1656
    # Add the texts that are not already present
1972
1657
    for path, ie in entries:
1973
 
        text_keys[(ie.file_id, ie.revision)] = ie
1974
 
    text_parent_map = repository.texts.get_parent_map(text_keys)
1975
 
    missing_texts = set(text_keys) - set(text_parent_map)
1976
 
    # Add the texts that are not already present
1977
 
    for text_key in missing_texts:
1978
 
        ie = text_keys[text_key]
1979
 
        text_parents = []
1980
 
        # FIXME: TODO: The following loop overlaps/duplicates that done by
1981
 
        # commit to determine parents. There is a latent/real bug here where
1982
 
        # the parents inserted are not those commit would do - in particular
1983
 
        # they are not filtered by heads(). RBC, AB
1984
 
        for revision, tree in parent_trees.iteritems():
1985
 
            if ie.file_id not in tree:
1986
 
                continue
1987
 
            parent_id = tree.inventory[ie.file_id].revision
1988
 
            if parent_id in text_parents:
1989
 
                continue
1990
 
            text_parents.append((ie.file_id, parent_id))
1991
 
        lines = revision_tree.get_file(ie.file_id).readlines()
1992
 
        repository.texts.add_lines(text_key, text_parents, lines)
 
1658
        w = repository.weave_store.get_weave_or_empty(ie.file_id,
 
1659
                repository.get_transaction())
 
1660
        if ie.revision not in w:
 
1661
            text_parents = []
 
1662
            # FIXME: TODO: The following loop *may* be overlapping/duplicate
 
1663
            # with InventoryEntry.find_previous_heads(). if it is, then there
 
1664
            # is a latent bug here where the parents may have ancestors of each
 
1665
            # other. RBC, AB
 
1666
            for revision, tree in parent_trees.iteritems():
 
1667
                if ie.file_id not in tree:
 
1668
                    continue
 
1669
                parent_id = tree.inventory[ie.file_id].revision
 
1670
                if parent_id in text_parents:
 
1671
                    continue
 
1672
                text_parents.append(parent_id)
 
1673
                    
 
1674
            vfile = repository.weave_store.get_weave_or_empty(ie.file_id, 
 
1675
                repository.get_transaction())
 
1676
            lines = revision_tree.get_file(ie.file_id).readlines()
 
1677
            vfile.add_lines(rev.revision_id, text_parents, lines)
1993
1678
    try:
1994
1679
        # install the inventory
1995
1680
        repository.add_inventory(rev.revision_id, inv, present_parents)
1996
1681
    except errors.RevisionAlreadyPresent:
1997
1682
        pass
1998
 
    if signature is not None:
1999
 
        repository.add_signature_text(rev.revision_id, signature)
2000
1683
    repository.add_revision(rev.revision_id, rev, inv)
2001
1684
 
2002
1685
 
2003
1686
class MetaDirRepository(Repository):
2004
 
    """Repositories in the new meta-dir layout.
2005
 
    
2006
 
    :ivar _transport: Transport for access to repository control files,
2007
 
        typically pointing to .bzr/repository.
2008
 
    """
2009
 
 
2010
 
    def __init__(self, _format, a_bzrdir, control_files):
2011
 
        super(MetaDirRepository, self).__init__(_format, a_bzrdir, control_files)
2012
 
        self._transport = control_files._transport
2013
 
 
 
1687
    """Repositories in the new meta-dir layout."""
 
1688
 
 
1689
    def __init__(self, _format, a_bzrdir, control_files, _revision_store, control_store, text_store):
 
1690
        super(MetaDirRepository, self).__init__(_format,
 
1691
                                                a_bzrdir,
 
1692
                                                control_files,
 
1693
                                                _revision_store,
 
1694
                                                control_store,
 
1695
                                                text_store)
 
1696
        dir_mode = self.control_files._dir_mode
 
1697
        file_mode = self.control_files._file_mode
 
1698
 
 
1699
    @needs_read_lock
2014
1700
    def is_shared(self):
2015
1701
        """Return True if this repository is flagged as a shared repository."""
2016
 
        return self._transport.has('shared-storage')
 
1702
        return self.control_files._transport.has('shared-storage')
2017
1703
 
2018
1704
    @needs_write_lock
2019
1705
    def set_make_working_trees(self, new_value):
2027
1713
        """
2028
1714
        if new_value:
2029
1715
            try:
2030
 
                self._transport.delete('no-working-trees')
 
1716
                self.control_files._transport.delete('no-working-trees')
2031
1717
            except errors.NoSuchFile:
2032
1718
                pass
2033
1719
        else:
2034
 
            self._transport.put_bytes('no-working-trees', '',
2035
 
                mode=self.bzrdir._get_file_mode())
 
1720
            self.control_files.put_utf8('no-working-trees', '')
2036
1721
    
2037
1722
    def make_working_trees(self):
2038
1723
        """Returns the policy for making working trees on new branches."""
2039
 
        return not self._transport.has('no-working-trees')
2040
 
 
2041
 
 
2042
 
class MetaDirVersionedFileRepository(MetaDirRepository):
2043
 
    """Repositories in a meta-dir, that work via versioned file objects."""
2044
 
 
2045
 
    def __init__(self, _format, a_bzrdir, control_files):
2046
 
        super(MetaDirVersionedFileRepository, self).__init__(_format, a_bzrdir,
2047
 
            control_files)
 
1724
        return not self.control_files._transport.has('no-working-trees')
2048
1725
 
2049
1726
 
2050
1727
class RepositoryFormatRegistry(registry.Registry):
2094
1771
    _matchingbzrdir - the bzrdir format that the repository format was
2095
1772
    originally written to work with. This can be used if manually
2096
1773
    constructing a bzrdir and repository, or more commonly for test suite
2097
 
    parameterization.
 
1774
    parameterisation.
2098
1775
    """
2099
1776
 
2100
1777
    # Set to True or False in derived classes. True indicates that the format
2101
1778
    # supports ghosts gracefully.
2102
1779
    supports_ghosts = None
2103
 
    # Can this repository be given external locations to lookup additional
2104
 
    # data. Set to True or False in derived classes.
2105
 
    supports_external_lookups = None
2106
1780
 
2107
1781
    def __str__(self):
2108
1782
        return "<%s>" % self.__class__.__name__
2129
1803
        except errors.NoSuchFile:
2130
1804
            raise errors.NoRepositoryPresent(a_bzrdir)
2131
1805
        except KeyError:
2132
 
            raise errors.UnknownFormatError(format=format_string,
2133
 
                                            kind='repository')
 
1806
            raise errors.UnknownFormatError(format=format_string)
2134
1807
 
2135
1808
    @classmethod
2136
1809
    def register_format(klass, format):
2146
1819
        from bzrlib import bzrdir
2147
1820
        return bzrdir.format_registry.make_bzrdir('default').repository_format
2148
1821
 
 
1822
    def _get_control_store(self, repo_transport, control_files):
 
1823
        """Return the control store for this repository."""
 
1824
        raise NotImplementedError(self._get_control_store)
 
1825
 
2149
1826
    def get_format_string(self):
2150
1827
        """Return the ASCII format string that identifies this format.
2151
1828
        
2158
1835
        """Return the short description for this format."""
2159
1836
        raise NotImplementedError(self.get_format_description)
2160
1837
 
 
1838
    def _get_revision_store(self, repo_transport, control_files):
 
1839
        """Return the revision store object for this a_bzrdir."""
 
1840
        raise NotImplementedError(self._get_revision_store)
 
1841
 
 
1842
    def _get_text_rev_store(self,
 
1843
                            transport,
 
1844
                            control_files,
 
1845
                            name,
 
1846
                            compressed=True,
 
1847
                            prefixed=False,
 
1848
                            serializer=None):
 
1849
        """Common logic for getting a revision store for a repository.
 
1850
        
 
1851
        see self._get_revision_store for the subclass-overridable method to 
 
1852
        get the store for a repository.
 
1853
        """
 
1854
        from bzrlib.store.revision.text import TextRevisionStore
 
1855
        dir_mode = control_files._dir_mode
 
1856
        file_mode = control_files._file_mode
 
1857
        text_store = TextStore(transport.clone(name),
 
1858
                              prefixed=prefixed,
 
1859
                              compressed=compressed,
 
1860
                              dir_mode=dir_mode,
 
1861
                              file_mode=file_mode)
 
1862
        _revision_store = TextRevisionStore(text_store, serializer)
 
1863
        return _revision_store
 
1864
 
2161
1865
    # TODO: this shouldn't be in the base class, it's specific to things that
2162
1866
    # use weaves or knits -- mbp 20070207
2163
1867
    def _get_versioned_file_store(self,
2217
1921
 
2218
1922
    rich_root_data = False
2219
1923
    supports_tree_reference = False
2220
 
    supports_external_lookups = False
2221
1924
    _matchingbzrdir = bzrdir.BzrDirMetaFormat1()
2222
1925
 
2223
1926
    def __init__(self):
2237
1940
        """Upload the initial blank content."""
2238
1941
        control_files = self._create_control_files(a_bzrdir)
2239
1942
        control_files.lock_write()
2240
 
        transport = control_files._transport
2241
 
        if shared == True:
2242
 
            utf8_files += [('shared-storage', '')]
2243
1943
        try:
2244
 
            transport.mkdir_multi(dirs, mode=a_bzrdir._get_dir_mode())
2245
 
            for (filename, content_stream) in files:
2246
 
                transport.put_file(filename, content_stream,
2247
 
                    mode=a_bzrdir._get_file_mode())
2248
 
            for (filename, content_bytes) in utf8_files:
2249
 
                transport.put_bytes_non_atomic(filename, content_bytes,
2250
 
                    mode=a_bzrdir._get_file_mode())
 
1944
            control_files._transport.mkdir_multi(dirs,
 
1945
                    mode=control_files._dir_mode)
 
1946
            for file, content in files:
 
1947
                control_files.put(file, content)
 
1948
            for file, content in utf8_files:
 
1949
                control_files.put_utf8(file, content)
 
1950
            if shared == True:
 
1951
                control_files.put_utf8('shared-storage', '')
2251
1952
        finally:
2252
1953
            control_files.unlock()
2253
1954
 
2264
1965
    'RepositoryFormat7'
2265
1966
    )
2266
1967
 
 
1968
# KEEP in sync with bzrdir.format_registry default, which controls the overall
 
1969
# default control directory format
2267
1970
format_registry.register_lazy(
2268
1971
    'Bazaar-NG Knit Repository Format 1',
2269
1972
    'bzrlib.repofmt.knitrepo',
2270
1973
    'RepositoryFormatKnit1',
2271
1974
    )
 
1975
format_registry.default_key = 'Bazaar-NG Knit Repository Format 1'
2272
1976
 
2273
1977
format_registry.register_lazy(
2274
1978
    'Bazaar Knit Repository Format 3 (bzr 0.15)\n',
2276
1980
    'RepositoryFormatKnit3',
2277
1981
    )
2278
1982
 
2279
 
format_registry.register_lazy(
2280
 
    'Bazaar Knit Repository Format 4 (bzr 1.0)\n',
2281
 
    'bzrlib.repofmt.knitrepo',
2282
 
    'RepositoryFormatKnit4',
2283
 
    )
2284
 
 
2285
1983
# Pack-based formats. There is one format for pre-subtrees, and one for
2286
1984
# post-subtrees to allow ease of testing.
2287
 
# NOTE: These are experimental in 0.92. Stable in 1.0 and above
 
1985
# NOTE: These are experimental in 0.92.
2288
1986
format_registry.register_lazy(
2289
1987
    'Bazaar pack repository format 1 (needs bzr 0.92)\n',
2290
1988
    'bzrlib.repofmt.pack_repo',
2295
1993
    'bzrlib.repofmt.pack_repo',
2296
1994
    'RepositoryFormatKnitPack3',
2297
1995
    )
2298
 
format_registry.register_lazy(
2299
 
    'Bazaar pack repository format 1 with rich root (needs bzr 1.0)\n',
2300
 
    'bzrlib.repofmt.pack_repo',
2301
 
    'RepositoryFormatKnitPack4',
2302
 
    )
2303
 
format_registry.register_lazy(
2304
 
    'Bazaar RepositoryFormatKnitPack5 (bzr 1.6)\n',
2305
 
    'bzrlib.repofmt.pack_repo',
2306
 
    'RepositoryFormatKnitPack5',
2307
 
    )
2308
 
format_registry.register_lazy(
2309
 
    'Bazaar RepositoryFormatKnitPack5RichRoot (bzr 1.6.1)\n',
2310
 
    'bzrlib.repofmt.pack_repo',
2311
 
    'RepositoryFormatKnitPack5RichRoot',
2312
 
    )
2313
 
format_registry.register_lazy(
2314
 
    'Bazaar RepositoryFormatKnitPack5RichRoot (bzr 1.6)\n',
2315
 
    'bzrlib.repofmt.pack_repo',
2316
 
    'RepositoryFormatKnitPack5RichRootBroken',
2317
 
    )
2318
 
 
2319
 
# Development formats. 
2320
 
# 1.5->1.6
2321
 
format_registry.register_lazy(
2322
 
    "Bazaar development format 1 (needs bzr.dev from before 1.6)\n",
2323
 
    'bzrlib.repofmt.pack_repo',
2324
 
    'RepositoryFormatPackDevelopment1',
2325
 
    )
2326
 
format_registry.register_lazy(
2327
 
    ("Bazaar development format 1 with subtree support "
2328
 
        "(needs bzr.dev from before 1.6)\n"),
2329
 
    'bzrlib.repofmt.pack_repo',
2330
 
    'RepositoryFormatPackDevelopment1Subtree',
2331
 
    )
2332
 
# 1.6->1.7 go below here
2333
1996
 
2334
1997
 
2335
1998
class InterRepository(InterObject):
2360
2023
        :param pb: optional progress bar to use for progress reports. If not
2361
2024
                   provided a default one will be created.
2362
2025
 
2363
 
        :returns: (copied_revision_count, failures).
2364
 
        """
2365
 
        # Normally we should find a specific InterRepository subclass to do
2366
 
        # the fetch; if nothing else then at least InterSameDataRepository.
2367
 
        # If none of them is suitable it looks like fetching is not possible;
2368
 
        # we try to give a good message why.  _assert_same_model will probably
2369
 
        # give a helpful message; otherwise a generic one.
2370
 
        self._assert_same_model(self.source, self.target)
2371
 
        raise errors.IncompatibleRepositories(self.source, self.target,
2372
 
            "no suitableInterRepository found")
2373
 
 
2374
 
    def _walk_to_common_revisions(self, revision_ids):
2375
 
        """Walk out from revision_ids in source to revisions target has.
2376
 
 
2377
 
        :param revision_ids: The start point for the search.
2378
 
        :return: A set of revision ids.
2379
 
        """
2380
 
        target_graph = self.target.get_graph()
2381
 
        revision_ids = frozenset(revision_ids)
2382
 
        if set(target_graph.get_parent_map(revision_ids)) == revision_ids:
2383
 
            return graph.SearchResult(revision_ids, set(), 0, set())
2384
 
        missing_revs = set()
2385
 
        source_graph = self.source.get_graph()
2386
 
        # ensure we don't pay silly lookup costs.
2387
 
        searcher = source_graph._make_breadth_first_searcher(revision_ids)
2388
 
        null_set = frozenset([_mod_revision.NULL_REVISION])
2389
 
        while True:
2390
 
            try:
2391
 
                next_revs, ghosts = searcher.next_with_ghosts()
2392
 
            except StopIteration:
2393
 
                break
2394
 
            if revision_ids.intersection(ghosts):
2395
 
                absent_ids = set(revision_ids.intersection(ghosts))
2396
 
                # If all absent_ids are present in target, no error is needed.
2397
 
                absent_ids.difference_update(
2398
 
                    set(target_graph.get_parent_map(absent_ids)))
2399
 
                if absent_ids:
2400
 
                    raise errors.NoSuchRevision(self.source, absent_ids.pop())
2401
 
            # we don't care about other ghosts as we can't fetch them and
2402
 
            # haven't been asked to.
2403
 
            next_revs = set(next_revs)
2404
 
            # we always have NULL_REVISION present.
2405
 
            have_revs = set(target_graph.get_parent_map(next_revs)).union(null_set)
2406
 
            missing_revs.update(next_revs - have_revs)
2407
 
            searcher.stop_searching_any(have_revs)
2408
 
        return searcher.get_result()
 
2026
        Returns the copied revision count and the failed revisions in a tuple:
 
2027
        (copied, failures).
 
2028
        """
 
2029
        raise NotImplementedError(self.fetch)
2409
2030
   
2410
 
    @deprecated_method(one_two)
2411
2031
    @needs_read_lock
2412
 
    def missing_revision_ids(self, revision_id=None, find_ghosts=True):
 
2032
    def missing_revision_ids(self, revision_id=None):
2413
2033
        """Return the revision ids that source has that target does not.
2414
2034
        
2415
2035
        These are returned in topological order.
2416
2036
 
2417
2037
        :param revision_id: only return revision ids included by this
2418
2038
                            revision_id.
2419
 
        :param find_ghosts: If True find missing revisions in deep history
2420
 
            rather than just finding the surface difference.
2421
 
        """
2422
 
        return list(self.search_missing_revision_ids(
2423
 
            revision_id, find_ghosts).get_keys())
2424
 
 
2425
 
    @needs_read_lock
2426
 
    def search_missing_revision_ids(self, revision_id=None, find_ghosts=True):
2427
 
        """Return the revision ids that source has that target does not.
2428
 
        
2429
 
        :param revision_id: only return revision ids included by this
2430
 
                            revision_id.
2431
 
        :param find_ghosts: If True find missing revisions in deep history
2432
 
            rather than just finding the surface difference.
2433
 
        :return: A bzrlib.graph.SearchResult.
2434
 
        """
2435
 
        # stop searching at found target revisions.
2436
 
        if not find_ghosts and revision_id is not None:
2437
 
            return self._walk_to_common_revisions([revision_id])
 
2039
        """
2438
2040
        # generic, possibly worst case, slow code path.
2439
2041
        target_ids = set(self.target.all_revision_ids())
2440
2042
        if revision_id is not None:
2441
2043
            source_ids = self.source.get_ancestry(revision_id)
2442
 
            if source_ids[0] is not None:
2443
 
                raise AssertionError()
 
2044
            assert source_ids[0] is None
2444
2045
            source_ids.pop(0)
2445
2046
        else:
2446
2047
            source_ids = self.source.all_revision_ids()
2447
2048
        result_set = set(source_ids).difference(target_ids)
2448
 
        return self.source.revision_ids_to_search_result(result_set)
 
2049
        # this may look like a no-op: its not. It preserves the ordering
 
2050
        # other_ids had while only returning the members from other_ids
 
2051
        # that we've decided we need.
 
2052
        return [rev_id for rev_id in source_ids if rev_id in result_set]
2449
2053
 
2450
2054
    @staticmethod
2451
2055
    def _same_model(source, target):
2452
 
        """True if source and target have the same data representation.
2453
 
        
2454
 
        Note: this is always called on the base class; overriding it in a
2455
 
        subclass will have no effect.
2456
 
        """
2457
 
        try:
2458
 
            InterRepository._assert_same_model(source, target)
2459
 
            return True
2460
 
        except errors.IncompatibleRepositories, e:
 
2056
        """True if source and target have the same data representation."""
 
2057
        if source.supports_rich_root() != target.supports_rich_root():
2461
2058
            return False
2462
 
 
2463
 
    @staticmethod
2464
 
    def _assert_same_model(source, target):
2465
 
        """Raise an exception if two repositories do not use the same model.
2466
 
        """
2467
 
        if source.supports_rich_root() != target.supports_rich_root():
2468
 
            raise errors.IncompatibleRepositories(source, target,
2469
 
                "different rich-root support")
2470
2059
        if source._serializer != target._serializer:
2471
 
            raise errors.IncompatibleRepositories(source, target,
2472
 
                "different serializers")
 
2060
            return False
 
2061
        return True
2473
2062
 
2474
2063
 
2475
2064
class InterSameDataRepository(InterRepository):
2518
2107
    @needs_write_lock
2519
2108
    def fetch(self, revision_id=None, pb=None, find_ghosts=False):
2520
2109
        """See InterRepository.fetch()."""
2521
 
        from bzrlib.fetch import RepoFetcher
 
2110
        from bzrlib.fetch import GenericRepoFetcher
2522
2111
        mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
2523
2112
               self.source, self.source._format, self.target,
2524
2113
               self.target._format)
2525
 
        f = RepoFetcher(to_repository=self.target,
 
2114
        f = GenericRepoFetcher(to_repository=self.target,
2526
2115
                               from_repository=self.source,
2527
2116
                               last_revision=revision_id,
2528
 
                               pb=pb, find_ghosts=find_ghosts)
 
2117
                               pb=pb)
2529
2118
        return f.count_copied, f.failed_revisions
2530
2119
 
2531
2120
 
2570
2159
        # weave specific optimised path:
2571
2160
        try:
2572
2161
            self.target.set_make_working_trees(self.source.make_working_trees())
2573
 
        except (errors.RepositoryUpgradeRequired, NotImplemented):
 
2162
        except NotImplementedError:
2574
2163
            pass
2575
2164
        # FIXME do not peek!
2576
 
        if self.source._transport.listable():
 
2165
        if self.source.control_files._transport.listable():
2577
2166
            pb = ui.ui_factory.nested_progress_bar()
2578
2167
            try:
2579
 
                self.target.texts.insert_record_stream(
2580
 
                    self.source.texts.get_record_stream(
2581
 
                        self.source.texts.keys(), 'topological', False))
 
2168
                self.target.weave_store.copy_all_ids(
 
2169
                    self.source.weave_store,
 
2170
                    pb=pb,
 
2171
                    from_transaction=self.source.get_transaction(),
 
2172
                    to_transaction=self.target.get_transaction())
2582
2173
                pb.update('copying inventory', 0, 1)
2583
 
                self.target.inventories.insert_record_stream(
2584
 
                    self.source.inventories.get_record_stream(
2585
 
                        self.source.inventories.keys(), 'topological', False))
2586
 
                self.target.signatures.insert_record_stream(
2587
 
                    self.source.signatures.get_record_stream(
2588
 
                        self.source.signatures.keys(),
2589
 
                        'unordered', True))
2590
 
                self.target.revisions.insert_record_stream(
2591
 
                    self.source.revisions.get_record_stream(
2592
 
                        self.source.revisions.keys(),
2593
 
                        'topological', True))
 
2174
                self.target.control_weaves.copy_multi(
 
2175
                    self.source.control_weaves, ['inventory'],
 
2176
                    from_transaction=self.source.get_transaction(),
 
2177
                    to_transaction=self.target.get_transaction())
 
2178
                self.target._revision_store.text_store.copy_all_ids(
 
2179
                    self.source._revision_store.text_store,
 
2180
                    pb=pb)
2594
2181
            finally:
2595
2182
                pb.finished()
2596
2183
        else:
2599
2186
    @needs_write_lock
2600
2187
    def fetch(self, revision_id=None, pb=None, find_ghosts=False):
2601
2188
        """See InterRepository.fetch()."""
2602
 
        from bzrlib.fetch import RepoFetcher
 
2189
        from bzrlib.fetch import GenericRepoFetcher
2603
2190
        mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
2604
2191
               self.source, self.source._format, self.target, self.target._format)
2605
 
        f = RepoFetcher(to_repository=self.target,
 
2192
        f = GenericRepoFetcher(to_repository=self.target,
2606
2193
                               from_repository=self.source,
2607
2194
                               last_revision=revision_id,
2608
 
                               pb=pb, find_ghosts=find_ghosts)
 
2195
                               pb=pb)
2609
2196
        return f.count_copied, f.failed_revisions
2610
2197
 
2611
2198
    @needs_read_lock
2612
 
    def search_missing_revision_ids(self, revision_id=None, find_ghosts=True):
 
2199
    def missing_revision_ids(self, revision_id=None):
2613
2200
        """See InterRepository.missing_revision_ids()."""
2614
2201
        # we want all revisions to satisfy revision_id in source.
2615
2202
        # but we don't want to stat every file here and there.
2624
2211
        # - RBC 20060209
2625
2212
        if revision_id is not None:
2626
2213
            source_ids = self.source.get_ancestry(revision_id)
2627
 
            if source_ids[0] is not None:
2628
 
                raise AssertionError()
 
2214
            assert source_ids[0] is None
2629
2215
            source_ids.pop(0)
2630
2216
        else:
2631
2217
            source_ids = self.source._all_possible_ids()
2636
2222
        # we do not have a revision as that would be pointless.
2637
2223
        target_ids = set(self.target._all_possible_ids())
2638
2224
        possibly_present_revisions = target_ids.intersection(source_ids_set)
2639
 
        actually_present_revisions = set(
2640
 
            self.target._eliminate_revisions_not_present(possibly_present_revisions))
 
2225
        actually_present_revisions = set(self.target._eliminate_revisions_not_present(possibly_present_revisions))
2641
2226
        required_revisions = source_ids_set.difference(actually_present_revisions)
 
2227
        required_topo_revisions = [rev_id for rev_id in source_ids if rev_id in required_revisions]
2642
2228
        if revision_id is not None:
2643
2229
            # we used get_ancestry to determine source_ids then we are assured all
2644
2230
            # revisions referenced are present as they are installed in topological order.
2645
2231
            # and the tip revision was validated by get_ancestry.
2646
 
            result_set = required_revisions
 
2232
            return required_topo_revisions
2647
2233
        else:
2648
2234
            # if we just grabbed the possibly available ids, then 
2649
2235
            # we only have an estimate of whats available and need to validate
2650
2236
            # that against the revision records.
2651
 
            result_set = set(
2652
 
                self.source._eliminate_revisions_not_present(required_revisions))
2653
 
        return self.source.revision_ids_to_search_result(result_set)
 
2237
            return self.source._eliminate_revisions_not_present(required_topo_revisions)
2654
2238
 
2655
2239
 
2656
2240
class InterKnitRepo(InterSameDataRepository):
2680
2264
    @needs_write_lock
2681
2265
    def fetch(self, revision_id=None, pb=None, find_ghosts=False):
2682
2266
        """See InterRepository.fetch()."""
2683
 
        from bzrlib.fetch import RepoFetcher
 
2267
        from bzrlib.fetch import KnitRepoFetcher
2684
2268
        mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
2685
2269
               self.source, self.source._format, self.target, self.target._format)
2686
 
        f = RepoFetcher(to_repository=self.target,
 
2270
        f = KnitRepoFetcher(to_repository=self.target,
2687
2271
                            from_repository=self.source,
2688
2272
                            last_revision=revision_id,
2689
 
                            pb=pb, find_ghosts=find_ghosts)
 
2273
                            pb=pb)
2690
2274
        return f.count_copied, f.failed_revisions
2691
2275
 
2692
2276
    @needs_read_lock
2693
 
    def search_missing_revision_ids(self, revision_id=None, find_ghosts=True):
 
2277
    def missing_revision_ids(self, revision_id=None):
2694
2278
        """See InterRepository.missing_revision_ids()."""
2695
2279
        if revision_id is not None:
2696
2280
            source_ids = self.source.get_ancestry(revision_id)
2697
 
            if source_ids[0] is not None:
2698
 
                raise AssertionError()
 
2281
            assert source_ids[0] is None
2699
2282
            source_ids.pop(0)
2700
2283
        else:
2701
2284
            source_ids = self.source.all_revision_ids()
2706
2289
        # we do not have a revision as that would be pointless.
2707
2290
        target_ids = set(self.target.all_revision_ids())
2708
2291
        possibly_present_revisions = target_ids.intersection(source_ids_set)
2709
 
        actually_present_revisions = set(
2710
 
            self.target._eliminate_revisions_not_present(possibly_present_revisions))
 
2292
        actually_present_revisions = set(self.target._eliminate_revisions_not_present(possibly_present_revisions))
2711
2293
        required_revisions = source_ids_set.difference(actually_present_revisions)
 
2294
        required_topo_revisions = [rev_id for rev_id in source_ids if rev_id in required_revisions]
2712
2295
        if revision_id is not None:
2713
2296
            # we used get_ancestry to determine source_ids then we are assured all
2714
2297
            # revisions referenced are present as they are installed in topological order.
2715
2298
            # and the tip revision was validated by get_ancestry.
2716
 
            result_set = required_revisions
 
2299
            return required_topo_revisions
2717
2300
        else:
2718
2301
            # if we just grabbed the possibly available ids, then 
2719
2302
            # we only have an estimate of whats available and need to validate
2720
2303
            # that against the revision records.
2721
 
            result_set = set(
2722
 
                self.source._eliminate_revisions_not_present(required_revisions))
2723
 
        return self.source.revision_ids_to_search_result(result_set)
 
2304
            return self.source._eliminate_revisions_not_present(required_topo_revisions)
2724
2305
 
2725
2306
 
2726
2307
class InterPackRepo(InterSameDataRepository):
2750
2331
    @needs_write_lock
2751
2332
    def fetch(self, revision_id=None, pb=None, find_ghosts=False):
2752
2333
        """See InterRepository.fetch()."""
2753
 
        if (len(self.source._fallback_repositories) > 0 or
2754
 
            len(self.target._fallback_repositories) > 0):
2755
 
            # The pack layer is not aware of fallback repositories, so when
2756
 
            # fetching from a stacked repository or into a stacked repository
2757
 
            # we use the generic fetch logic which uses the VersionedFiles
2758
 
            # attributes on repository.
2759
 
            from bzrlib.fetch import RepoFetcher
2760
 
            fetcher = RepoFetcher(self.target, self.source, revision_id,
2761
 
                                  pb, find_ghosts)
2762
 
            return fetcher.count_copied, fetcher.failed_revisions
2763
 
        from bzrlib.repofmt.pack_repo import Packer
2764
2334
        mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
2765
2335
               self.source, self.source._format, self.target, self.target._format)
2766
2336
        self.count_copied = 0
2770
2340
            # to fetch from all packs to one without
2771
2341
            # inventory parsing etc, IFF nothing to be copied is in the target.
2772
2342
            # till then:
2773
 
            source_revision_ids = frozenset(self.source.all_revision_ids())
2774
 
            revision_ids = source_revision_ids - \
2775
 
                frozenset(self.target.get_parent_map(source_revision_ids))
2776
 
            revision_keys = [(revid,) for revid in revision_ids]
2777
 
            index = self.target._pack_collection.revision_index.combined_index
2778
 
            present_revision_ids = set(item[1][0] for item in
2779
 
                index.iter_entries(revision_keys))
2780
 
            revision_ids = set(revision_ids) - present_revision_ids
 
2343
            revision_ids = self.source.all_revision_ids()
2781
2344
            # implementing the TODO will involve:
2782
2345
            # - detecting when all of a pack is selected
2783
2346
            # - avoiding as much as possible pre-selection, so the
2787
2350
            # sensibly detect 'new revisions' without doing a full index scan.
2788
2351
        elif _mod_revision.is_null(revision_id):
2789
2352
            # nothing to do:
2790
 
            return (0, [])
 
2353
            return
2791
2354
        else:
2792
2355
            try:
2793
 
                revision_ids = self.search_missing_revision_ids(revision_id,
2794
 
                    find_ghosts=find_ghosts).get_keys()
 
2356
                revision_ids = self.missing_revision_ids(revision_id,
 
2357
                    find_ghosts=find_ghosts)
2795
2358
            except errors.NoSuchRevision:
2796
2359
                raise errors.InstallFailed([revision_id])
2797
 
            if len(revision_ids) == 0:
2798
 
                return (0, [])
2799
2360
        packs = self.source._pack_collection.all_packs()
2800
 
        pack = Packer(self.target._pack_collection, packs, '.fetch',
2801
 
            revision_ids).pack()
 
2361
        pack = self.target._pack_collection.create_pack_from_packs(
 
2362
            packs, '.fetch', revision_ids,
 
2363
            )
2802
2364
        if pack is not None:
2803
2365
            self.target._pack_collection._save_pack_names()
2804
2366
            # Trigger an autopack. This may duplicate effort as we've just done
2805
2367
            # a pack creation, but for now it is simpler to think about as
2806
2368
            # 'upload data, then repack if needed'.
2807
2369
            self.target._pack_collection.autopack()
2808
 
            return (pack.get_revision_count(), [])
 
2370
            return pack.get_revision_count()
2809
2371
        else:
2810
 
            return (0, [])
 
2372
            return 0
2811
2373
 
2812
2374
    @needs_read_lock
2813
 
    def search_missing_revision_ids(self, revision_id=None, find_ghosts=True):
 
2375
    def missing_revision_ids(self, revision_id=None, find_ghosts=True):
2814
2376
        """See InterRepository.missing_revision_ids().
2815
2377
        
2816
 
        :param find_ghosts: Find ghosts throughout the ancestry of
 
2378
        :param find_ghosts: Find ghosts throughough the ancestry of
2817
2379
            revision_id.
2818
2380
        """
2819
2381
        if not find_ghosts and revision_id is not None:
2820
 
            return self._walk_to_common_revisions([revision_id])
2821
 
        elif revision_id is not None:
2822
 
            # Find ghosts: search for revisions pointing from one repository to
2823
 
            # the other, and vice versa, anywhere in the history of revision_id.
2824
 
            graph = self.target.get_graph(other_repository=self.source)
 
2382
            graph = self.source.get_graph()
 
2383
            missing_revs = set()
2825
2384
            searcher = graph._make_breadth_first_searcher([revision_id])
2826
 
            found_ids = set()
 
2385
            target_index = \
 
2386
                self.target._pack_collection.revision_index.combined_index
 
2387
            null_set = frozenset([_mod_revision.NULL_REVISION])
2827
2388
            while True:
2828
2389
                try:
2829
 
                    next_revs, ghosts = searcher.next_with_ghosts()
 
2390
                    next_revs = set(searcher.next())
2830
2391
                except StopIteration:
2831
2392
                    break
2832
 
                if revision_id in ghosts:
2833
 
                    raise errors.NoSuchRevision(self.source, revision_id)
2834
 
                found_ids.update(next_revs)
2835
 
                found_ids.update(ghosts)
2836
 
            found_ids = frozenset(found_ids)
2837
 
            # Double query here: should be able to avoid this by changing the
2838
 
            # graph api further.
2839
 
            result_set = found_ids - frozenset(
2840
 
                self.target.get_parent_map(found_ids))
 
2393
                next_revs.difference_update(null_set)
 
2394
                target_keys = [(key,) for key in next_revs]
 
2395
                have_revs = frozenset(node[1][0] for node in
 
2396
                    target_index.iter_entries(target_keys))
 
2397
                missing_revs.update(next_revs - have_revs)
 
2398
                searcher.stop_searching_any(have_revs)
 
2399
            return missing_revs
 
2400
        elif revision_id is not None:
 
2401
            source_ids = self.source.get_ancestry(revision_id)
 
2402
            assert source_ids[0] is None
 
2403
            source_ids.pop(0)
2841
2404
        else:
2842
2405
            source_ids = self.source.all_revision_ids()
2843
 
            # source_ids is the worst possible case we may need to pull.
2844
 
            # now we want to filter source_ids against what we actually
2845
 
            # have in target, but don't try to check for existence where we know
2846
 
            # we do not have a revision as that would be pointless.
2847
 
            target_ids = set(self.target.all_revision_ids())
2848
 
            result_set = set(source_ids).difference(target_ids)
2849
 
        return self.source.revision_ids_to_search_result(result_set)
 
2406
        # source_ids is the worst possible case we may need to pull.
 
2407
        # now we want to filter source_ids against what we actually
 
2408
        # have in target, but don't try to check for existence where we know
 
2409
        # we do not have a revision as that would be pointless.
 
2410
        target_ids = set(self.target.all_revision_ids())
 
2411
        return [r for r in source_ids if (r not in target_ids)]
2850
2412
 
2851
2413
 
2852
2414
class InterModel1and2(InterRepository):
2869
2431
        f = Model1toKnit2Fetcher(to_repository=self.target,
2870
2432
                                 from_repository=self.source,
2871
2433
                                 last_revision=revision_id,
2872
 
                                 pb=pb, find_ghosts=find_ghosts)
 
2434
                                 pb=pb)
2873
2435
        return f.count_copied, f.failed_revisions
2874
2436
 
2875
2437
    @needs_write_lock
2902
2464
    @staticmethod
2903
2465
    def is_compatible(source, target):
2904
2466
        """Be compatible with Knit1 source and Knit3 target"""
 
2467
        from bzrlib.repofmt.knitrepo import RepositoryFormatKnit3
2905
2468
        try:
2906
 
            from bzrlib.repofmt.knitrepo import (
2907
 
                RepositoryFormatKnit1,
2908
 
                RepositoryFormatKnit3,
2909
 
                )
2910
 
            from bzrlib.repofmt.pack_repo import (
2911
 
                RepositoryFormatKnitPack1,
2912
 
                RepositoryFormatKnitPack3,
2913
 
                RepositoryFormatKnitPack4,
2914
 
                RepositoryFormatKnitPack5,
2915
 
                RepositoryFormatKnitPack5RichRoot,
2916
 
                RepositoryFormatPackDevelopment1,
2917
 
                RepositoryFormatPackDevelopment1Subtree,
2918
 
                )
2919
 
            norichroot = (
2920
 
                RepositoryFormatKnit1,            # no rr, no subtree
2921
 
                RepositoryFormatKnitPack1,        # no rr, no subtree
2922
 
                RepositoryFormatPackDevelopment1, # no rr, no subtree
2923
 
                RepositoryFormatKnitPack5,        # no rr, no subtree
2924
 
                )
2925
 
            richroot = (
2926
 
                RepositoryFormatKnit3,            # rr, subtree
2927
 
                RepositoryFormatKnitPack3,        # rr, subtree
2928
 
                RepositoryFormatKnitPack4,        # rr, no subtree
2929
 
                RepositoryFormatKnitPack5RichRoot,# rr, no subtree
2930
 
                RepositoryFormatPackDevelopment1Subtree, # rr, subtree
2931
 
                )
2932
 
            for format in norichroot:
2933
 
                if format.rich_root_data:
2934
 
                    raise AssertionError('Format %s is a rich-root format'
2935
 
                        ' but is included in the non-rich-root list'
2936
 
                        % (format,))
2937
 
            for format in richroot:
2938
 
                if not format.rich_root_data:
2939
 
                    raise AssertionError('Format %s is not a rich-root format'
2940
 
                        ' but is included in the rich-root list'
2941
 
                        % (format,))
2942
 
            # TODO: One alternative is to just check format.rich_root_data,
2943
 
            #       instead of keeping membership lists. However, the formats
2944
 
            #       *also* have to use the same 'Knit' style of storage
2945
 
            #       (line-deltas, fulltexts, etc.)
2946
 
            return (isinstance(source._format, norichroot) and
2947
 
                    isinstance(target._format, richroot))
 
2469
            from bzrlib.repofmt.knitrepo import (RepositoryFormatKnit1,
 
2470
                RepositoryFormatKnit3)
 
2471
            from bzrlib.repofmt.pack_repo import (RepositoryFormatKnitPack1,
 
2472
                RepositoryFormatKnitPack3)
 
2473
            return (isinstance(source._format,
 
2474
                    (RepositoryFormatKnit1, RepositoryFormatKnitPack1)) and
 
2475
                isinstance(target._format,
 
2476
                    (RepositoryFormatKnit3, RepositoryFormatKnitPack3))
 
2477
                )
2948
2478
        except AttributeError:
2949
2479
            return False
2950
2480
 
2958
2488
        f = Knit1to2Fetcher(to_repository=self.target,
2959
2489
                            from_repository=self.source,
2960
2490
                            last_revision=revision_id,
2961
 
                            pb=pb, find_ghosts=find_ghosts)
 
2491
                            pb=pb)
2962
2492
        return f.count_copied, f.failed_revisions
2963
2493
 
2964
2494
 
2965
 
class InterDifferingSerializer(InterKnitRepo):
 
2495
class InterRemoteToOther(InterRepository):
2966
2496
 
2967
 
    @classmethod
2968
 
    def _get_repo_format_to_test(self):
2969
 
        return None
 
2497
    def __init__(self, source, target):
 
2498
        InterRepository.__init__(self, source, target)
 
2499
        self._real_inter = None
2970
2500
 
2971
2501
    @staticmethod
2972
2502
    def is_compatible(source, target):
2973
 
        """Be compatible with Knit2 source and Knit3 target"""
2974
 
        if source.supports_rich_root() != target.supports_rich_root():
2975
 
            return False
2976
 
        # Ideally, we'd support fetching if the source had no tree references
2977
 
        # even if it supported them...
2978
 
        if (getattr(source, '_format.supports_tree_reference', False) and
2979
 
            not getattr(target, '_format.supports_tree_reference', False)):
2980
 
            return False
2981
 
        return True
 
2503
        if not isinstance(source, remote.RemoteRepository):
 
2504
            return False
 
2505
        source._ensure_real()
 
2506
        real_source = source._real_repository
 
2507
        # Is source's model compatible with target's model, and are they the
 
2508
        # same format?  Currently we can only optimise fetching from an
 
2509
        # identical model & format repo.
 
2510
        assert not isinstance(real_source, remote.RemoteRepository), (
 
2511
            "We don't support remote repos backed by remote repos yet.")
 
2512
        return real_source._format == target._format
2982
2513
 
2983
2514
    @needs_write_lock
2984
2515
    def fetch(self, revision_id=None, pb=None, find_ghosts=False):
2985
2516
        """See InterRepository.fetch()."""
2986
 
        revision_ids = self.target.search_missing_revision_ids(self.source,
2987
 
            revision_id, find_ghosts=find_ghosts).get_keys()
2988
 
        revision_ids = tsort.topo_sort(
2989
 
            self.source.get_graph().get_parent_map(revision_ids))
2990
 
        def revisions_iterator():
2991
 
            for current_revision_id in revision_ids:
2992
 
                revision = self.source.get_revision(current_revision_id)
2993
 
                tree = self.source.revision_tree(current_revision_id)
2994
 
                try:
2995
 
                    signature = self.source.get_signature_text(
2996
 
                        current_revision_id)
2997
 
                except errors.NoSuchRevision:
2998
 
                    signature = None
2999
 
                yield revision, tree, signature
3000
 
        if pb is None:
3001
 
            my_pb = ui.ui_factory.nested_progress_bar()
3002
 
            pb = my_pb
3003
 
        else:
3004
 
            my_pb = None
3005
 
        try:
3006
 
            install_revisions(self.target, revisions_iterator(),
3007
 
                              len(revision_ids), pb)
3008
 
        finally:
3009
 
            if my_pb is not None:
3010
 
                my_pb.finished()
3011
 
        return len(revision_ids), 0
 
2517
        from bzrlib.fetch import RemoteToOtherFetcher
 
2518
        mutter("Using fetch logic to copy between %s(remote) and %s(%s)",
 
2519
               self.source, self.target, self.target._format)
 
2520
        # TODO: jam 20070210 This should be an assert, not a translate
 
2521
        revision_id = osutils.safe_revision_id(revision_id)
 
2522
        f = RemoteToOtherFetcher(to_repository=self.target,
 
2523
                                 from_repository=self.source,
 
2524
                                 last_revision=revision_id,
 
2525
                                 pb=pb)
 
2526
        return f.count_copied, f.failed_revisions
 
2527
 
 
2528
    @classmethod
 
2529
    def _get_repo_format_to_test(self):
 
2530
        return None
3012
2531
 
3013
2532
 
3014
2533
class InterOtherToRemote(InterRepository):
3035
2554
 
3036
2555
    def fetch(self, revision_id=None, pb=None, find_ghosts=False):
3037
2556
        self._ensure_real_inter()
3038
 
        return self._real_inter.fetch(revision_id=revision_id, pb=pb,
3039
 
            find_ghosts=find_ghosts)
3040
 
 
3041
 
    @classmethod
3042
 
    def _get_repo_format_to_test(self):
3043
 
        return None
3044
 
 
3045
 
 
3046
 
class InterRemoteToOther(InterRepository):
3047
 
 
3048
 
    def __init__(self, source, target):
3049
 
        InterRepository.__init__(self, source, target)
3050
 
        self._real_inter = None
3051
 
 
3052
 
    @staticmethod
3053
 
    def is_compatible(source, target):
3054
 
        if not isinstance(source, remote.RemoteRepository):
3055
 
            return False
3056
 
        # Is source's model compatible with target's model?
3057
 
        source._ensure_real()
3058
 
        real_source = source._real_repository
3059
 
        if isinstance(real_source, remote.RemoteRepository):
3060
 
            raise NotImplementedError(
3061
 
                "We don't support remote repos backed by remote repos yet.")
3062
 
        return InterRepository._same_model(real_source, target)
3063
 
 
3064
 
    def _ensure_real_inter(self):
3065
 
        if self._real_inter is None:
3066
 
            self.source._ensure_real()
3067
 
            real_source = self.source._real_repository
3068
 
            self._real_inter = InterRepository.get(real_source, self.target)
3069
 
    
3070
 
    def fetch(self, revision_id=None, pb=None, find_ghosts=False):
3071
 
        self._ensure_real_inter()
3072
 
        return self._real_inter.fetch(revision_id=revision_id, pb=pb,
3073
 
            find_ghosts=find_ghosts)
3074
 
 
3075
 
    def copy_content(self, revision_id=None):
3076
 
        self._ensure_real_inter()
3077
 
        self._real_inter.copy_content(revision_id=revision_id)
3078
 
 
3079
 
    @classmethod
3080
 
    def _get_repo_format_to_test(self):
3081
 
        return None
3082
 
 
3083
 
 
3084
 
 
3085
 
InterRepository.register_optimiser(InterDifferingSerializer)
 
2557
        self._real_inter.fetch(revision_id=revision_id, pb=pb)
 
2558
 
 
2559
    @classmethod
 
2560
    def _get_repo_format_to_test(self):
 
2561
        return None
 
2562
 
 
2563
 
3086
2564
InterRepository.register_optimiser(InterSameDataRepository)
3087
2565
InterRepository.register_optimiser(InterWeaveRepo)
3088
2566
InterRepository.register_optimiser(InterKnitRepo)
3089
2567
InterRepository.register_optimiser(InterModel1and2)
3090
2568
InterRepository.register_optimiser(InterKnit1and2)
3091
2569
InterRepository.register_optimiser(InterPackRepo)
 
2570
InterRepository.register_optimiser(InterRemoteToOther)
3092
2571
InterRepository.register_optimiser(InterOtherToRemote)
3093
 
InterRepository.register_optimiser(InterRemoteToOther)
3094
2572
 
3095
2573
 
3096
2574
class CopyConverter(object):
3175
2653
    return _unescape_re.sub(_unescaper, data)
3176
2654
 
3177
2655
 
3178
 
class _VersionedFileChecker(object):
 
2656
class _RevisionTextVersionCache(object):
 
2657
    """A cache of the versionedfile versions for revision and file-id."""
3179
2658
 
3180
2659
    def __init__(self, repository):
3181
2660
        self.repository = repository
3182
 
        self.text_index = self.repository._generate_text_key_index()
 
2661
        self.revision_versions = {}
 
2662
        self.revision_parents = {}
 
2663
        self.repo_graph = self.repository.get_graph()
 
2664
        # XXX: RBC: I haven't tracked down what uses this, but it would be
 
2665
        # better to use the headscache directly I think.
 
2666
        self.heads = graph.HeadsCache(self.repo_graph).heads
 
2667
 
 
2668
    def add_revision_text_versions(self, tree):
 
2669
        """Cache text version data from the supplied revision tree"""
 
2670
        inv_revisions = {}
 
2671
        for path, entry in tree.iter_entries_by_dir():
 
2672
            inv_revisions[entry.file_id] = entry.revision
 
2673
        self.revision_versions[tree.get_revision_id()] = inv_revisions
 
2674
        return inv_revisions
 
2675
 
 
2676
    def get_text_version(self, file_id, revision_id):
 
2677
        """Determine the text version for a given file-id and revision-id"""
 
2678
        try:
 
2679
            inv_revisions = self.revision_versions[revision_id]
 
2680
        except KeyError:
 
2681
            try:
 
2682
                tree = self.repository.revision_tree(revision_id)
 
2683
            except errors.RevisionNotPresent:
 
2684
                self.revision_versions[revision_id] = inv_revisions = {}
 
2685
            else:
 
2686
                inv_revisions = self.add_revision_text_versions(tree)
 
2687
        return inv_revisions.get(file_id)
 
2688
 
 
2689
    def prepopulate_revs(self, revision_ids):
 
2690
        # Filter out versions that we don't have an inventory for, so that the
 
2691
        # revision_trees() call won't fail.
 
2692
        inv_weave = self.repository.get_inventory_weave()
 
2693
        revs = [r for r in revision_ids if inv_weave.has_version(r)]
 
2694
        # XXX: this loop is very similar to
 
2695
        # bzrlib.fetch.Inter1and2Helper.iter_rev_trees.
 
2696
        while revs:
 
2697
            mutter('%d revisions left to prepopulate', len(revs))
 
2698
            for tree in self.repository.revision_trees(revs[:100]):
 
2699
                if tree.inventory.revision_id is None:
 
2700
                    tree.inventory.revision_id = tree.get_revision_id()
 
2701
                self.add_revision_text_versions(tree)
 
2702
            revs = revs[100:]
 
2703
 
 
2704
    def get_parents(self, revision_id):
 
2705
        try:
 
2706
            return self.revision_parents[revision_id]
 
2707
        except KeyError:
 
2708
            parents = self.repository.get_parents([revision_id])[0]
 
2709
            self.revision_parents[revision_id] = parents
 
2710
            return parents
 
2711
 
 
2712
    def used_file_versions(self):
 
2713
        """Return a set of (revision_id, file_id) pairs for each file version
 
2714
        referenced by any inventory cached by this _RevisionTextVersionCache.
 
2715
 
 
2716
        If the entire repository has been cached, this can be used to find all
 
2717
        file versions that are actually referenced by inventories.  Thus any
 
2718
        other file version is completely unused and can be removed safely.
 
2719
        """
 
2720
        result = set()
 
2721
        for inventory_summary in self.revision_versions.itervalues():
 
2722
            result.update(inventory_summary.items())
 
2723
        return result
 
2724
 
 
2725
 
 
2726
class VersionedFileChecker(object):
 
2727
 
 
2728
    def __init__(self, planned_revisions, revision_versions, repository):
 
2729
        self.planned_revisions = planned_revisions
 
2730
        self.revision_versions = revision_versions
 
2731
        self.repository = repository
3183
2732
    
3184
 
    def calculate_file_version_parents(self, text_key):
 
2733
    def calculate_file_version_parents(self, revision_id, file_id):
3185
2734
        """Calculate the correct parents for a file version according to
3186
2735
        the inventories.
3187
2736
        """
3188
 
        parent_keys = self.text_index[text_key]
3189
 
        if parent_keys == [_mod_revision.NULL_REVISION]:
3190
 
            return ()
3191
 
        return tuple(parent_keys)
 
2737
        text_revision = self.revision_versions.get_text_version(
 
2738
            file_id, revision_id)
 
2739
        if text_revision is None:
 
2740
            return None
 
2741
        parents_of_text_revision = self.revision_versions.get_parents(
 
2742
            text_revision)
 
2743
        parents_from_inventories = []
 
2744
        for parent in parents_of_text_revision:
 
2745
            if parent == _mod_revision.NULL_REVISION:
 
2746
                continue
 
2747
            introduced_in = self.revision_versions.get_text_version(file_id,
 
2748
                    parent)
 
2749
            if introduced_in is not None:
 
2750
                parents_from_inventories.append(introduced_in)
 
2751
        heads = set(self.revision_versions.heads(parents_from_inventories))
 
2752
        new_parents = []
 
2753
        for parent in parents_from_inventories:
 
2754
            if parent in heads and parent not in new_parents:
 
2755
                new_parents.append(parent)
 
2756
        return tuple(new_parents)
3192
2757
 
3193
 
    def check_file_version_parents(self, texts, progress_bar=None):
 
2758
    def check_file_version_parents(self, weave, file_id):
3194
2759
        """Check the parents stored in a versioned file are correct.
3195
2760
 
3196
2761
        It also detects file versions that are not referenced by their
3204
2769
            file, but not used by the corresponding inventory.
3205
2770
        """
3206
2771
        wrong_parents = {}
3207
 
        self.file_ids = set([file_id for file_id, _ in
3208
 
            self.text_index.iterkeys()])
3209
 
        # text keys is now grouped by file_id
3210
 
        n_weaves = len(self.file_ids)
3211
 
        files_in_revisions = {}
3212
 
        revisions_of_files = {}
3213
 
        n_versions = len(self.text_index)
3214
 
        progress_bar.update('loading text store', 0, n_versions)
3215
 
        parent_map = self.repository.texts.get_parent_map(self.text_index)
3216
 
        # On unlistable transports this could well be empty/error...
3217
 
        text_keys = self.repository.texts.keys()
3218
 
        unused_keys = frozenset(text_keys) - set(self.text_index)
3219
 
        for num, key in enumerate(self.text_index.iterkeys()):
3220
 
            if progress_bar is not None:
3221
 
                progress_bar.update('checking text graph', num, n_versions)
3222
 
            correct_parents = self.calculate_file_version_parents(key)
 
2772
        dangling_file_versions = set()
 
2773
        for num, revision_id in enumerate(self.planned_revisions):
 
2774
            correct_parents = self.calculate_file_version_parents(
 
2775
                revision_id, file_id)
 
2776
            if correct_parents is None:
 
2777
                continue
 
2778
            text_revision = self.revision_versions.get_text_version(
 
2779
                file_id, revision_id)
3223
2780
            try:
3224
 
                knit_parents = parent_map[key]
 
2781
                knit_parents = tuple(weave.get_parents(revision_id))
3225
2782
            except errors.RevisionNotPresent:
3226
 
                # Missing text!
3227
2783
                knit_parents = None
 
2784
            if text_revision != revision_id:
 
2785
                # This file version is not referenced by its corresponding
 
2786
                # inventory!
 
2787
                dangling_file_versions.add((file_id, revision_id))
3228
2788
            if correct_parents != knit_parents:
3229
 
                wrong_parents[key] = (knit_parents, correct_parents)
3230
 
        return wrong_parents, unused_keys
3231
 
 
3232
 
 
3233
 
def _old_get_graph(repository, revision_id):
3234
 
    """DO NOT USE. That is all. I'm serious."""
3235
 
    graph = repository.get_graph()
3236
 
    revision_graph = dict(((key, value) for key, value in
3237
 
        graph.iter_ancestry([revision_id]) if value is not None))
3238
 
    return _strip_NULL_ghosts(revision_graph)
3239
 
 
3240
 
 
3241
 
def _strip_NULL_ghosts(revision_graph):
3242
 
    """Also don't use this. more compatibility code for unmigrated clients."""
3243
 
    # Filter ghosts, and null:
3244
 
    if _mod_revision.NULL_REVISION in revision_graph:
3245
 
        del revision_graph[_mod_revision.NULL_REVISION]
3246
 
    for key, parents in revision_graph.items():
3247
 
        revision_graph[key] = tuple(parent for parent in parents if parent
3248
 
            in revision_graph)
3249
 
    return revision_graph
 
2789
                wrong_parents[revision_id] = (knit_parents, correct_parents)
 
2790
        return wrong_parents, dangling_file_versions