~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/repository.py

  • Committer: Jonathan Lange
  • Date: 2009-06-26 08:46:52 UTC
  • mto: (4484.1.1 bring-1.16.1-back)
  • mto: This revision was merged to the branch mainline in revision 4485.
  • Revision ID: jml@canonical.com-20090626084652-x7wn8yimd3fj0j0y
Tweak NEWS slightly based on mbp's feedback.

Show diffs side-by-side

added added

removed removed

Lines of Context:
494
494
            ie.executable = content_summary[2]
495
495
            file_obj, stat_value = tree.get_file_with_stat(ie.file_id, path)
496
496
            try:
497
 
                text = file_obj.read()
 
497
                lines = file_obj.readlines()
498
498
            finally:
499
499
                file_obj.close()
500
500
            try:
501
501
                ie.text_sha1, ie.text_size = self._add_text_to_weave(
502
 
                    ie.file_id, text, heads, nostore_sha)
 
502
                    ie.file_id, lines, heads, nostore_sha)
503
503
                # Let the caller know we generated a stat fingerprint.
504
504
                fingerprint = (ie.text_sha1, stat_value)
505
505
            except errors.ExistingContent:
517
517
                # carry over:
518
518
                ie.revision = parent_entry.revision
519
519
                return self._get_delta(ie, basis_inv, path), False, None
520
 
            self._add_text_to_weave(ie.file_id, '', heads, None)
 
520
            lines = []
 
521
            self._add_text_to_weave(ie.file_id, lines, heads, None)
521
522
        elif kind == 'symlink':
522
523
            current_link_target = content_summary[3]
523
524
            if not store:
531
532
                ie.symlink_target = parent_entry.symlink_target
532
533
                return self._get_delta(ie, basis_inv, path), False, None
533
534
            ie.symlink_target = current_link_target
534
 
            self._add_text_to_weave(ie.file_id, '', heads, None)
 
535
            lines = []
 
536
            self._add_text_to_weave(ie.file_id, lines, heads, None)
535
537
        elif kind == 'tree-reference':
536
538
            if not store:
537
539
                if content_summary[3] != parent_entry.reference_revision:
542
544
                ie.revision = parent_entry.revision
543
545
                return self._get_delta(ie, basis_inv, path), False, None
544
546
            ie.reference_revision = content_summary[3]
545
 
            self._add_text_to_weave(ie.file_id, '', heads, None)
 
547
            lines = []
 
548
            self._add_text_to_weave(ie.file_id, lines, heads, None)
546
549
        else:
547
550
            raise NotImplementedError('unknown kind')
548
551
        ie.revision = self._new_revision_id
742
745
                        entry.executable = True
743
746
                    else:
744
747
                        entry.executable = False
745
 
                    if (carry_over_possible and
 
748
                    if (carry_over_possible and 
746
749
                        parent_entry.executable == entry.executable):
747
750
                            # Check the file length, content hash after reading
748
751
                            # the file.
751
754
                        nostore_sha = None
752
755
                    file_obj, stat_value = tree.get_file_with_stat(file_id, change[1][1])
753
756
                    try:
754
 
                        text = file_obj.read()
 
757
                        lines = file_obj.readlines()
755
758
                    finally:
756
759
                        file_obj.close()
757
760
                    try:
758
761
                        entry.text_sha1, entry.text_size = self._add_text_to_weave(
759
 
                            file_id, text, heads, nostore_sha)
 
762
                            file_id, lines, heads, nostore_sha)
760
763
                        yield file_id, change[1][1], (entry.text_sha1, stat_value)
761
764
                    except errors.ExistingContent:
762
765
                        # No content change against a carry_over parent
771
774
                        parent_entry.symlink_target == entry.symlink_target):
772
775
                        carried_over = True
773
776
                    else:
774
 
                        self._add_text_to_weave(change[0], '', heads, None)
 
777
                        self._add_text_to_weave(change[0], [], heads, None)
775
778
                elif kind == 'directory':
776
779
                    if carry_over_possible:
777
780
                        carried_over = True
779
782
                        # Nothing to set on the entry.
780
783
                        # XXX: split into the Root and nonRoot versions.
781
784
                        if change[1][1] != '' or self.repository.supports_rich_root():
782
 
                            self._add_text_to_weave(change[0], '', heads, None)
 
785
                            self._add_text_to_weave(change[0], [], heads, None)
783
786
                elif kind == 'tree-reference':
784
787
                    if not self.repository._format.supports_tree_reference:
785
788
                        # This isn't quite sane as an error, but we shouldn't
794
797
                        parent_entry.reference_revision == reference_revision):
795
798
                        carried_over = True
796
799
                    else:
797
 
                        self._add_text_to_weave(change[0], '', heads, None)
 
800
                        self._add_text_to_weave(change[0], [], heads, None)
798
801
                else:
799
802
                    raise AssertionError('unknown kind %r' % kind)
800
803
                if not carried_over:
815
818
            self._require_root_change(tree)
816
819
        self.basis_delta_revision = basis_revision_id
817
820
 
818
 
    def _add_text_to_weave(self, file_id, new_text, parents, nostore_sha):
819
 
        parent_keys = tuple([(file_id, parent) for parent in parents])
820
 
        return self.repository.texts._add_text(
821
 
            (file_id, self._new_revision_id), parent_keys, new_text,
822
 
            nostore_sha=nostore_sha, random_id=self.random_revid)[0:2]
 
821
    def _add_text_to_weave(self, file_id, new_lines, parents, nostore_sha):
 
822
        # Note: as we read the content directly from the tree, we know its not
 
823
        # been turned into unicode or badly split - but a broken tree
 
824
        # implementation could give us bad output from readlines() so this is
 
825
        # not a guarantee of safety. What would be better is always checking
 
826
        # the content during test suite execution. RBC 20070912
 
827
        parent_keys = tuple((file_id, parent) for parent in parents)
 
828
        return self.repository.texts.add_lines(
 
829
            (file_id, self._new_revision_id), parent_keys, new_lines,
 
830
            nostore_sha=nostore_sha, random_id=self.random_revid,
 
831
            check_content=False)[0:2]
823
832
 
824
833
 
825
834
class RootCommitBuilder(CommitBuilder):
1911
1920
                    yield line, revid
1912
1921
 
1913
1922
    def _find_file_ids_from_xml_inventory_lines(self, line_iterator,
1914
 
        revision_keys):
 
1923
        revision_ids):
1915
1924
        """Helper routine for fileids_altered_by_revision_ids.
1916
1925
 
1917
1926
        This performs the translation of xml lines to revision ids.
1918
1927
 
1919
1928
        :param line_iterator: An iterator of lines, origin_version_id
1920
 
        :param revision_keys: The revision ids to filter for. This should be a
 
1929
        :param revision_ids: The revision ids to filter for. This should be a
1921
1930
            set or other type which supports efficient __contains__ lookups, as
1922
 
            the revision key from each parsed line will be looked up in the
1923
 
            revision_keys filter.
 
1931
            the revision id from each parsed line will be looked up in the
 
1932
            revision_ids filter.
1924
1933
        :return: a dictionary mapping altered file-ids to an iterable of
1925
1934
        revision_ids. Each altered file-ids has the exact revision_ids that
1926
1935
        altered it listed explicitly.
1927
1936
        """
1928
1937
        seen = set(self._find_text_key_references_from_xml_inventory_lines(
1929
1938
                line_iterator).iterkeys())
1930
 
        parent_keys = self._find_parent_keys_of_revisions(revision_keys)
 
1939
        # Note that revision_ids are revision keys.
 
1940
        parent_maps = self.revisions.get_parent_map(revision_ids)
 
1941
        parents = set()
 
1942
        map(parents.update, parent_maps.itervalues())
 
1943
        parents.difference_update(revision_ids)
1931
1944
        parent_seen = set(self._find_text_key_references_from_xml_inventory_lines(
1932
 
            self._inventory_xml_lines_for_keys(parent_keys)))
 
1945
            self._inventory_xml_lines_for_keys(parents)))
1933
1946
        new_keys = seen - parent_seen
1934
1947
        result = {}
1935
1948
        setdefault = result.setdefault
1937
1950
            setdefault(key[0], set()).add(key[-1])
1938
1951
        return result
1939
1952
 
1940
 
    def _find_parent_ids_of_revisions(self, revision_ids):
1941
 
        """Find all parent ids that are mentioned in the revision graph.
1942
 
 
1943
 
        :return: set of revisions that are parents of revision_ids which are
1944
 
            not part of revision_ids themselves
1945
 
        """
1946
 
        parent_map = self.get_parent_map(revision_ids)
1947
 
        parent_ids = set()
1948
 
        map(parent_ids.update, parent_map.itervalues())
1949
 
        parent_ids.difference_update(revision_ids)
1950
 
        parent_ids.discard(_mod_revision.NULL_REVISION)
1951
 
        return parent_ids
1952
 
 
1953
 
    def _find_parent_keys_of_revisions(self, revision_keys):
1954
 
        """Similar to _find_parent_ids_of_revisions, but used with keys.
1955
 
 
1956
 
        :param revision_keys: An iterable of revision_keys.
1957
 
        :return: The parents of all revision_keys that are not already in
1958
 
            revision_keys
1959
 
        """
1960
 
        parent_map = self.revisions.get_parent_map(revision_keys)
1961
 
        parent_keys = set()
1962
 
        map(parent_keys.update, parent_map.itervalues())
1963
 
        parent_keys.difference_update(revision_keys)
1964
 
        parent_keys.discard(_mod_revision.NULL_REVISION)
1965
 
        return parent_keys
1966
 
 
1967
1953
    def fileids_altered_by_revision_ids(self, revision_ids, _inv_weave=None):
1968
1954
        """Find the file ids and versions affected by revisions.
1969
1955
 
2249
2235
        """
2250
2236
        return self.get_revision(revision_id).inventory_sha1
2251
2237
 
2252
 
    def get_rev_id_for_revno(self, revno, known_pair):
2253
 
        """Return the revision id of a revno, given a later (revno, revid)
2254
 
        pair in the same history.
2255
 
 
2256
 
        :return: if found (True, revid).  If the available history ran out
2257
 
            before reaching the revno, then this returns
2258
 
            (False, (closest_revno, closest_revid)).
2259
 
        """
2260
 
        known_revno, known_revid = known_pair
2261
 
        partial_history = [known_revid]
2262
 
        distance_from_known = known_revno - revno
2263
 
        if distance_from_known < 0:
2264
 
            raise ValueError(
2265
 
                'requested revno (%d) is later than given known revno (%d)'
2266
 
                % (revno, known_revno))
2267
 
        try:
2268
 
            _iter_for_revno(
2269
 
                self, partial_history, stop_index=distance_from_known)
2270
 
        except errors.RevisionNotPresent, err:
2271
 
            if err.revision_id == known_revid:
2272
 
                # The start revision (known_revid) wasn't found.
2273
 
                raise
2274
 
            # This is a stacked repository with no fallbacks, or a there's a
2275
 
            # left-hand ghost.  Either way, even though the revision named in
2276
 
            # the error isn't in this repo, we know it's the next step in this
2277
 
            # left-hand history.
2278
 
            partial_history.append(err.revision_id)
2279
 
        if len(partial_history) <= distance_from_known:
2280
 
            # Didn't find enough history to get a revid for the revno.
2281
 
            earliest_revno = known_revno - len(partial_history) + 1
2282
 
            return (False, (earliest_revno, partial_history[-1]))
2283
 
        if len(partial_history) - 1 > distance_from_known:
2284
 
            raise AssertionError('_iter_for_revno returned too much history')
2285
 
        return (True, partial_history[-1])
2286
 
 
2287
2238
    def iter_reverse_revision_history(self, revision_id):
2288
2239
        """Iterate backwards through revision ids in the lefthand history
2289
2240
 
3480
3431
        return self.source.revision_ids_to_search_result(result_set)
3481
3432
 
3482
3433
 
 
3434
class InterPackRepo(InterSameDataRepository):
 
3435
    """Optimised code paths between Pack based repositories."""
 
3436
 
 
3437
    @classmethod
 
3438
    def _get_repo_format_to_test(self):
 
3439
        from bzrlib.repofmt import pack_repo
 
3440
        return pack_repo.RepositoryFormatKnitPack6RichRoot()
 
3441
 
 
3442
    @staticmethod
 
3443
    def is_compatible(source, target):
 
3444
        """Be compatible with known Pack formats.
 
3445
 
 
3446
        We don't test for the stores being of specific types because that
 
3447
        could lead to confusing results, and there is no need to be
 
3448
        overly general.
 
3449
 
 
3450
        InterPackRepo does not support CHK based repositories.
 
3451
        """
 
3452
        from bzrlib.repofmt.pack_repo import RepositoryFormatPack
 
3453
        from bzrlib.repofmt.groupcompress_repo import RepositoryFormatCHK1
 
3454
        try:
 
3455
            are_packs = (isinstance(source._format, RepositoryFormatPack) and
 
3456
                isinstance(target._format, RepositoryFormatPack))
 
3457
            not_packs = (isinstance(source._format, RepositoryFormatCHK1) or
 
3458
                isinstance(target._format, RepositoryFormatCHK1))
 
3459
        except AttributeError:
 
3460
            return False
 
3461
        if not_packs or not are_packs:
 
3462
            return False
 
3463
        return InterRepository._same_model(source, target)
 
3464
 
 
3465
    @needs_write_lock
 
3466
    def fetch(self, revision_id=None, pb=None, find_ghosts=False,
 
3467
            fetch_spec=None):
 
3468
        """See InterRepository.fetch()."""
 
3469
        if (len(self.source._fallback_repositories) > 0 or
 
3470
            len(self.target._fallback_repositories) > 0):
 
3471
            # The pack layer is not aware of fallback repositories, so when
 
3472
            # fetching from a stacked repository or into a stacked repository
 
3473
            # we use the generic fetch logic which uses the VersionedFiles
 
3474
            # attributes on repository.
 
3475
            from bzrlib.fetch import RepoFetcher
 
3476
            fetcher = RepoFetcher(self.target, self.source, revision_id,
 
3477
                    pb, find_ghosts, fetch_spec=fetch_spec)
 
3478
        if fetch_spec is not None:
 
3479
            if len(list(fetch_spec.heads)) != 1:
 
3480
                raise AssertionError(
 
3481
                    "InterPackRepo.fetch doesn't support "
 
3482
                    "fetching multiple heads yet.")
 
3483
            revision_id = list(fetch_spec.heads)[0]
 
3484
            fetch_spec = None
 
3485
        if revision_id is None:
 
3486
            # TODO:
 
3487
            # everything to do - use pack logic
 
3488
            # to fetch from all packs to one without
 
3489
            # inventory parsing etc, IFF nothing to be copied is in the target.
 
3490
            # till then:
 
3491
            source_revision_ids = frozenset(self.source.all_revision_ids())
 
3492
            revision_ids = source_revision_ids - \
 
3493
                frozenset(self.target.get_parent_map(source_revision_ids))
 
3494
            revision_keys = [(revid,) for revid in revision_ids]
 
3495
            index = self.target._pack_collection.revision_index.combined_index
 
3496
            present_revision_ids = set(item[1][0] for item in
 
3497
                index.iter_entries(revision_keys))
 
3498
            revision_ids = set(revision_ids) - present_revision_ids
 
3499
            # implementing the TODO will involve:
 
3500
            # - detecting when all of a pack is selected
 
3501
            # - avoiding as much as possible pre-selection, so the
 
3502
            # more-core routines such as create_pack_from_packs can filter in
 
3503
            # a just-in-time fashion. (though having a HEADS list on a
 
3504
            # repository might make this a lot easier, because we could
 
3505
            # sensibly detect 'new revisions' without doing a full index scan.
 
3506
        elif _mod_revision.is_null(revision_id):
 
3507
            # nothing to do:
 
3508
            return (0, [])
 
3509
        else:
 
3510
            revision_ids = self.search_missing_revision_ids(revision_id,
 
3511
                find_ghosts=find_ghosts).get_keys()
 
3512
            if len(revision_ids) == 0:
 
3513
                return (0, [])
 
3514
        return self._pack(self.source, self.target, revision_ids)
 
3515
 
 
3516
    def _pack(self, source, target, revision_ids):
 
3517
        from bzrlib.repofmt.pack_repo import Packer
 
3518
        packs = source._pack_collection.all_packs()
 
3519
        pack = Packer(self.target._pack_collection, packs, '.fetch',
 
3520
            revision_ids).pack()
 
3521
        if pack is not None:
 
3522
            self.target._pack_collection._save_pack_names()
 
3523
            copied_revs = pack.get_revision_count()
 
3524
            # Trigger an autopack. This may duplicate effort as we've just done
 
3525
            # a pack creation, but for now it is simpler to think about as
 
3526
            # 'upload data, then repack if needed'.
 
3527
            self.target._pack_collection.autopack()
 
3528
            return (copied_revs, [])
 
3529
        else:
 
3530
            return (0, [])
 
3531
 
 
3532
    @needs_read_lock
 
3533
    def search_missing_revision_ids(self, revision_id=None, find_ghosts=True):
 
3534
        """See InterRepository.missing_revision_ids().
 
3535
 
 
3536
        :param find_ghosts: Find ghosts throughout the ancestry of
 
3537
            revision_id.
 
3538
        """
 
3539
        if not find_ghosts and revision_id is not None:
 
3540
            return self._walk_to_common_revisions([revision_id])
 
3541
        elif revision_id is not None:
 
3542
            # Find ghosts: search for revisions pointing from one repository to
 
3543
            # the other, and vice versa, anywhere in the history of revision_id.
 
3544
            graph = self.target.get_graph(other_repository=self.source)
 
3545
            searcher = graph._make_breadth_first_searcher([revision_id])
 
3546
            found_ids = set()
 
3547
            while True:
 
3548
                try:
 
3549
                    next_revs, ghosts = searcher.next_with_ghosts()
 
3550
                except StopIteration:
 
3551
                    break
 
3552
                if revision_id in ghosts:
 
3553
                    raise errors.NoSuchRevision(self.source, revision_id)
 
3554
                found_ids.update(next_revs)
 
3555
                found_ids.update(ghosts)
 
3556
            found_ids = frozenset(found_ids)
 
3557
            # Double query here: should be able to avoid this by changing the
 
3558
            # graph api further.
 
3559
            result_set = found_ids - frozenset(
 
3560
                self.target.get_parent_map(found_ids))
 
3561
        else:
 
3562
            source_ids = self.source.all_revision_ids()
 
3563
            # source_ids is the worst possible case we may need to pull.
 
3564
            # now we want to filter source_ids against what we actually
 
3565
            # have in target, but don't try to check for existence where we know
 
3566
            # we do not have a revision as that would be pointless.
 
3567
            target_ids = set(self.target.all_revision_ids())
 
3568
            result_set = set(source_ids).difference(target_ids)
 
3569
        return self.source.revision_ids_to_search_result(result_set)
 
3570
 
 
3571
 
3483
3572
class InterDifferingSerializer(InterRepository):
3484
3573
 
3485
3574
    @classmethod
3765
3854
InterRepository.register_optimiser(InterSameDataRepository)
3766
3855
InterRepository.register_optimiser(InterWeaveRepo)
3767
3856
InterRepository.register_optimiser(InterKnitRepo)
 
3857
InterRepository.register_optimiser(InterPackRepo)
3768
3858
 
3769
3859
 
3770
3860
class CopyConverter(object):
4348
4438
            yield versionedfile.FulltextContentFactory(
4349
4439
                key, parent_keys, None, as_bytes)
4350
4440
 
4351
 
 
4352
 
def _iter_for_revno(repo, partial_history_cache, stop_index=None,
4353
 
                    stop_revision=None):
4354
 
    """Extend the partial history to include a given index
4355
 
 
4356
 
    If a stop_index is supplied, stop when that index has been reached.
4357
 
    If a stop_revision is supplied, stop when that revision is
4358
 
    encountered.  Otherwise, stop when the beginning of history is
4359
 
    reached.
4360
 
 
4361
 
    :param stop_index: The index which should be present.  When it is
4362
 
        present, history extension will stop.
4363
 
    :param stop_revision: The revision id which should be present.  When
4364
 
        it is encountered, history extension will stop.
4365
 
    """
4366
 
    start_revision = partial_history_cache[-1]
4367
 
    iterator = repo.iter_reverse_revision_history(start_revision)
4368
 
    try:
4369
 
        #skip the last revision in the list
4370
 
        iterator.next()
4371
 
        while True:
4372
 
            if (stop_index is not None and
4373
 
                len(partial_history_cache) > stop_index):
4374
 
                break
4375
 
            if partial_history_cache[-1] == stop_revision:
4376
 
                break
4377
 
            revision_id = iterator.next()
4378
 
            partial_history_cache.append(revision_id)
4379
 
    except StopIteration:
4380
 
        # No more history
4381
 
        return
4382