/brz/remove-bazaar

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

« back to all changes in this revision

Viewing changes to bzrlib/repository.py

merge bzr.dev r4164

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, 2008, 2009 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
34
34
    lockdir,
35
35
    lru_cache,
36
36
    osutils,
37
 
    remote,
38
37
    revision as _mod_revision,
39
38
    symbol_versioning,
40
39
    tsort,
884
883
 
885
884
        XXX: this docstring is duplicated in many places, e.g. lockable_files.py
886
885
        """
 
886
        locked = self.is_locked()
887
887
        result = self.control_files.lock_write(token=token)
888
888
        for repo in self._fallback_repositories:
889
889
            # Writes don't affect fallback repos
890
890
            repo.lock_read()
891
 
        self._refresh_data()
 
891
        if not locked:
 
892
            self._refresh_data()
892
893
        return result
893
894
 
894
895
    def lock_read(self):
 
896
        locked = self.is_locked()
895
897
        self.control_files.lock_read()
896
898
        for repo in self._fallback_repositories:
897
899
            repo.lock_read()
898
 
        self._refresh_data()
 
900
        if not locked:
 
901
            self._refresh_data()
899
902
 
900
903
    def get_physical_lock_status(self):
901
904
        return self.control_files.get_physical_lock_status()
1084
1087
    def suspend_write_group(self):
1085
1088
        raise errors.UnsuspendableWriteGroup(self)
1086
1089
 
 
1090
    def refresh_data(self):
 
1091
        """Re-read any data needed to to synchronise with disk.
 
1092
 
 
1093
        This method is intended to be called after another repository instance
 
1094
        (such as one used by a smart server) has inserted data into the
 
1095
        repository. It may not be called during a write group, but may be
 
1096
        called at any other time.
 
1097
        """
 
1098
        if self.is_in_write_group():
 
1099
            raise errors.InternalBzrError(
 
1100
                "May not refresh_data while in a write group.")
 
1101
        self._refresh_data()
 
1102
 
1087
1103
    def resume_write_group(self, tokens):
1088
1104
        if not self.is_write_locked():
1089
1105
            raise errors.NotWriteLocked(self)
1103
1119
        If revision_id is None and fetch_spec is None, then all content is
1104
1120
        copied.
1105
1121
 
 
1122
        fetch() may not be used when the repository is in a write group -
 
1123
        either finish the current write group before using fetch, or use
 
1124
        fetch before starting the write group.
 
1125
 
1106
1126
        :param find_ghosts: Find and copy revisions in the source that are
1107
1127
            ghosts in the target (and not reachable directly by walking out to
1108
1128
            the first-present revision in target from revision_id).
1117
1137
        if fetch_spec is not None and revision_id is not None:
1118
1138
            raise AssertionError(
1119
1139
                "fetch_spec and revision_id are mutually exclusive.")
 
1140
        if self.is_in_write_group():
 
1141
            raise errors.InternalBzrError(
 
1142
                "May not fetch while in a write group.")
1120
1143
        # fast path same-url fetch operations
1121
1144
        if self.has_same_location(source) and fetch_spec is None:
1122
1145
            # check that last_revision is in 'from' and then return a
1356
1379
    def find_text_key_references(self):
1357
1380
        """Find the text key references within the repository.
1358
1381
 
1359
 
        :return: a dictionary mapping (file_id, revision_id) tuples to altered file-ids to an iterable of
1360
 
        revision_ids. Each altered file-ids has the exact revision_ids that
1361
 
        altered it listed explicitly.
1362
1382
        :return: A dictionary mapping text keys ((fileid, revision_id) tuples)
1363
1383
            to whether they were referred to by the inventory of the
1364
1384
            revision_id that they contain. The inventory texts from all present
1453
1473
                result[key] = True
1454
1474
        return result
1455
1475
 
 
1476
    def _inventory_xml_lines_for_keys(self, keys):
 
1477
        """Get a line iterator of the sort needed for findind references.
 
1478
 
 
1479
        Not relevant for non-xml inventory repositories.
 
1480
 
 
1481
        Ghosts in revision_keys are ignored.
 
1482
 
 
1483
        :param revision_keys: The revision keys for the inventories to inspect.
 
1484
        :return: An iterator over (inventory line, revid) for the fulltexts of
 
1485
            all of the xml inventories specified by revision_keys.
 
1486
        """
 
1487
        stream = self.inventories.get_record_stream(keys, 'unordered', True)
 
1488
        for record in stream:
 
1489
            if record.storage_kind != 'absent':
 
1490
                chunks = record.get_bytes_as('chunked')
 
1491
                revid = record.key[-1]
 
1492
                lines = osutils.chunks_to_lines(chunks)
 
1493
                for line in lines:
 
1494
                    yield line, revid
 
1495
 
1456
1496
    def _find_file_ids_from_xml_inventory_lines(self, line_iterator,
1457
1497
        revision_ids):
1458
1498
        """Helper routine for fileids_altered_by_revision_ids.
1468
1508
        revision_ids. Each altered file-ids has the exact revision_ids that
1469
1509
        altered it listed explicitly.
1470
1510
        """
 
1511
        seen = set(self._find_text_key_references_from_xml_inventory_lines(
 
1512
                line_iterator).iterkeys())
 
1513
        # Note that revision_ids are revision keys.
 
1514
        parent_maps = self.revisions.get_parent_map(revision_ids)
 
1515
        parents = set()
 
1516
        map(parents.update, parent_maps.itervalues())
 
1517
        parents.difference_update(revision_ids)
 
1518
        parent_seen = set(self._find_text_key_references_from_xml_inventory_lines(
 
1519
            self._inventory_xml_lines_for_keys(parents)))
 
1520
        new_keys = seen - parent_seen
1471
1521
        result = {}
1472
1522
        setdefault = result.setdefault
1473
 
        for key in \
1474
 
            self._find_text_key_references_from_xml_inventory_lines(
1475
 
                line_iterator).iterkeys():
1476
 
            # once data is all ensured-consistent; then this is
1477
 
            # if revision_id == version_id
1478
 
            if key[-1:] in revision_ids:
1479
 
                setdefault(key[0], set()).add(key[-1])
 
1523
        for key in new_keys:
 
1524
            setdefault(key[0], set()).add(key[-1])
1480
1525
        return result
1481
1526
 
1482
1527
    def fileids_altered_by_revision_ids(self, revision_ids, _inv_weave=None):
1580
1625
        batch_size = 10 # should be ~150MB on a 55K path tree
1581
1626
        batch_count = len(revision_order) / batch_size + 1
1582
1627
        processed_texts = 0
1583
 
        pb.update("Calculating text parents.", processed_texts, text_count)
 
1628
        pb.update("Calculating text parents", processed_texts, text_count)
1584
1629
        for offset in xrange(batch_count):
1585
1630
            to_query = revision_order[offset * batch_size:(offset + 1) *
1586
1631
                batch_size]
1590
1635
                revision_id = rev_tree.get_revision_id()
1591
1636
                parent_ids = ancestors[revision_id]
1592
1637
                for text_key in revision_keys[revision_id]:
1593
 
                    pb.update("Calculating text parents.", processed_texts)
 
1638
                    pb.update("Calculating text parents", processed_texts)
1594
1639
                    processed_texts += 1
1595
1640
                    candidate_parents = []
1596
1641
                    for parent_id in parent_ids:
1813
1858
        for repositories to maintain loaded indices across multiple locks
1814
1859
        by checking inside their implementation of this method to see
1815
1860
        whether their indices are still valid. This depends of course on
1816
 
        the disk format being validatable in this manner.
 
1861
        the disk format being validatable in this manner. This method is
 
1862
        also called by the refresh_data() public interface to cause a refresh
 
1863
        to occur while in a write lock so that data inserted by a smart server
 
1864
        push operation is visible on the client's instance of the physical
 
1865
        repository.
1817
1866
        """
1818
1867
 
1819
1868
    @needs_read_lock
1943
1992
                [parents_provider, other_repository._make_parents_provider()])
1944
1993
        return graph.Graph(parents_provider)
1945
1994
 
1946
 
    def _get_versioned_file_checker(self):
1947
 
        """Return an object suitable for checking versioned files."""
1948
 
        return _VersionedFileChecker(self)
 
1995
    def _get_versioned_file_checker(self, text_key_references=None):
 
1996
        """Return an object suitable for checking versioned files.
 
1997
        
 
1998
        :param text_key_references: if non-None, an already built
 
1999
            dictionary mapping text keys ((fileid, revision_id) tuples)
 
2000
            to whether they were referred to by the inventory of the
 
2001
            revision_id that they contain. If None, this will be
 
2002
            calculated.
 
2003
        """
 
2004
        return _VersionedFileChecker(self,
 
2005
            text_key_references=text_key_references)
1949
2006
 
1950
2007
    def revision_ids_to_search_result(self, result_set):
1951
2008
        """Convert a set of revision ids to a graph SearchResult."""
2565
2622
    InterRepository.get(other).method_name(parameters).
2566
2623
    """
2567
2624
 
2568
 
    _walk_to_common_revisions_batch_size = 1
 
2625
    _walk_to_common_revisions_batch_size = 50
2569
2626
    _optimisers = []
2570
2627
    """The available optimised InterRepository types."""
2571
2628
 
2572
 
    def __init__(self, source, target):
2573
 
        InterObject.__init__(self, source, target)
2574
 
        # These two attributes may be overridden by e.g. InterOtherToRemote to
2575
 
        # provide a faster implementation.
2576
 
        self.target_get_graph = self.target.get_graph
2577
 
        self.target_get_parent_map = self.target.get_parent_map
2578
 
 
2579
2629
    @needs_write_lock
2580
2630
    def copy_content(self, revision_id=None):
2581
2631
        """Make a complete copy of the content in self into destination.
2592
2642
            pass
2593
2643
        self.target.fetch(self.source, revision_id=revision_id)
2594
2644
 
 
2645
    @needs_write_lock
2595
2646
    def fetch(self, revision_id=None, pb=None, find_ghosts=False,
2596
2647
            fetch_spec=None):
2597
2648
        """Fetch the content required to construct revision_id.
2605
2656
        :return: None.
2606
2657
        """
2607
2658
        from bzrlib.fetch import RepoFetcher
2608
 
        mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
2609
 
               self.source, self.source._format, self.target,
2610
 
               self.target._format)
2611
2659
        f = RepoFetcher(to_repository=self.target,
2612
2660
                               from_repository=self.source,
2613
2661
                               last_revision=revision_id,
2620
2668
        :param revision_ids: The start point for the search.
2621
2669
        :return: A set of revision ids.
2622
2670
        """
2623
 
        target_graph = self.target_get_graph()
 
2671
        target_graph = self.target.get_graph()
2624
2672
        revision_ids = frozenset(revision_ids)
2625
2673
        # Fast path for the case where all the revisions are already in the
2626
2674
        # target repo.
2826
2874
        else:
2827
2875
            self.target.fetch(self.source, revision_id=revision_id)
2828
2876
 
2829
 
    @needs_write_lock
2830
 
    def fetch(self, revision_id=None, pb=None, find_ghosts=False,
2831
 
            fetch_spec=None):
2832
 
        """See InterRepository.fetch()."""
2833
 
        from bzrlib.fetch import RepoFetcher
2834
 
        mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
2835
 
               self.source, self.source._format, self.target, self.target._format)
2836
 
        f = RepoFetcher(to_repository=self.target,
2837
 
                               from_repository=self.source,
2838
 
                               last_revision=revision_id,
2839
 
                               fetch_spec=fetch_spec,
2840
 
                               pb=pb, find_ghosts=find_ghosts)
2841
 
 
2842
2877
    @needs_read_lock
2843
2878
    def search_missing_revision_ids(self, revision_id=None, find_ghosts=True):
2844
2879
        """See InterRepository.missing_revision_ids()."""
2908
2943
            return False
2909
2944
        return are_knits and InterRepository._same_model(source, target)
2910
2945
 
2911
 
    @needs_write_lock
2912
 
    def fetch(self, revision_id=None, pb=None, find_ghosts=False,
2913
 
            fetch_spec=None):
2914
 
        """See InterRepository.fetch()."""
2915
 
        from bzrlib.fetch import RepoFetcher
2916
 
        mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
2917
 
               self.source, self.source._format, self.target, self.target._format)
2918
 
        f = RepoFetcher(to_repository=self.target,
2919
 
                            from_repository=self.source,
2920
 
                            last_revision=revision_id,
2921
 
                            fetch_spec=fetch_spec,
2922
 
                            pb=pb, find_ghosts=find_ghosts)
2923
 
 
2924
2946
    @needs_read_lock
2925
2947
    def search_missing_revision_ids(self, revision_id=None, find_ghosts=True):
2926
2948
        """See InterRepository.missing_revision_ids()."""
2992
3014
            from bzrlib.fetch import RepoFetcher
2993
3015
            fetcher = RepoFetcher(self.target, self.source, revision_id,
2994
3016
                    pb, find_ghosts, fetch_spec=fetch_spec)
2995
 
        mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
2996
 
               self.source, self.source._format, self.target, self.target._format)
2997
3017
        if fetch_spec is not None:
2998
3018
            if len(list(fetch_spec.heads)) != 1:
2999
3019
                raise AssertionError(
3000
3020
                    "InterPackRepo.fetch doesn't support "
3001
3021
                    "fetching multiple heads yet.")
3002
 
            revision_id = fetch_spec.heads[0]
 
3022
            revision_id = list(fetch_spec.heads)[0]
3003
3023
            fetch_spec = None
3004
3024
        if revision_id is None:
3005
3025
            # TODO:
3009
3029
            # till then:
3010
3030
            source_revision_ids = frozenset(self.source.all_revision_ids())
3011
3031
            revision_ids = source_revision_ids - \
3012
 
                frozenset(self.target_get_parent_map(source_revision_ids))
 
3032
                frozenset(self.target.get_parent_map(source_revision_ids))
3013
3033
            revision_keys = [(revid,) for revid in revision_ids]
3014
 
            target_pack_collection = self._get_target_pack_collection()
3015
 
            index = target_pack_collection.revision_index.combined_index
 
3034
            index = self.target._pack_collection.revision_index.combined_index
3016
3035
            present_revision_ids = set(item[1][0] for item in
3017
3036
                index.iter_entries(revision_keys))
3018
3037
            revision_ids = set(revision_ids) - present_revision_ids
3038
3057
 
3039
3058
    def _pack(self, source, target, revision_ids):
3040
3059
        from bzrlib.repofmt.pack_repo import Packer
3041
 
        target_pack_collection = self._get_target_pack_collection()
3042
3060
        packs = source._pack_collection.all_packs()
3043
 
        pack = Packer(target_pack_collection, packs, '.fetch',
 
3061
        pack = Packer(self.target._pack_collection, packs, '.fetch',
3044
3062
            revision_ids).pack()
3045
3063
        if pack is not None:
3046
 
            target_pack_collection._save_pack_names()
 
3064
            self.target._pack_collection._save_pack_names()
3047
3065
            copied_revs = pack.get_revision_count()
3048
3066
            # Trigger an autopack. This may duplicate effort as we've just done
3049
3067
            # a pack creation, but for now it is simpler to think about as
3050
3068
            # 'upload data, then repack if needed'.
3051
 
            self._autopack()
 
3069
            self.target._pack_collection.autopack()
3052
3070
            return (copied_revs, [])
3053
3071
        else:
3054
3072
            return (0, [])
3055
3073
 
3056
 
    def _autopack(self):
3057
 
        self.target._pack_collection.autopack()
3058
 
 
3059
 
    def _get_target_pack_collection(self):
3060
 
        return self.target._pack_collection
3061
 
 
3062
3074
    @needs_read_lock
3063
3075
    def search_missing_revision_ids(self, revision_id=None, find_ghosts=True):
3064
3076
        """See InterRepository.missing_revision_ids().
3071
3083
        elif revision_id is not None:
3072
3084
            # Find ghosts: search for revisions pointing from one repository to
3073
3085
            # the other, and vice versa, anywhere in the history of revision_id.
3074
 
            graph = self.target_get_graph(other_repository=self.source)
 
3086
            graph = self.target.get_graph(other_repository=self.source)
3075
3087
            searcher = graph._make_breadth_first_searcher([revision_id])
3076
3088
            found_ids = set()
3077
3089
            while True:
3087
3099
            # Double query here: should be able to avoid this by changing the
3088
3100
            # graph api further.
3089
3101
            result_set = found_ids - frozenset(
3090
 
                self.target_get_parent_map(found_ids))
 
3102
                self.target.get_parent_map(found_ids))
3091
3103
        else:
3092
3104
            source_ids = self.source.all_revision_ids()
3093
3105
            # source_ids is the worst possible case we may need to pull.
3165
3177
                        # We don't copy the text for the root node unless the
3166
3178
                        # target supports_rich_root.
3167
3179
                        continue
3168
 
                    # TODO: Do we need:
3169
 
                    #       "if entry.revision == current_revision_id" ?
3170
 
                    if entry.revision == current_revision_id:
3171
 
                        text_keys.add((file_id, entry.revision))
 
3180
                    text_keys.add((file_id, entry.revision))
3172
3181
            revision = self.source.get_revision(current_revision_id)
3173
3182
            pending_deltas.append((basis_id, delta,
3174
3183
                current_revision_id, revision.parent_ids))
3240
3249
            my_pb = ui.ui_factory.nested_progress_bar()
3241
3250
            pb = my_pb
3242
3251
        else:
 
3252
            symbol_versioning.warn(
 
3253
                symbol_versioning.deprecated_in((1, 14, 0))
 
3254
                % "pb parameter to fetch()")
3243
3255
            my_pb = None
3244
3256
        try:
3245
3257
            self._fetch_all_revisions(revision_ids, pb)
3271
3283
        return basis_id, basis_tree
3272
3284
 
3273
3285
 
3274
 
class InterOtherToRemote(InterRepository):
3275
 
    """An InterRepository that simply delegates to the 'real' InterRepository
3276
 
    calculated for (source, target._real_repository).
3277
 
    """
3278
 
 
3279
 
    _walk_to_common_revisions_batch_size = 50
3280
 
 
3281
 
    def __init__(self, source, target):
3282
 
        InterRepository.__init__(self, source, target)
3283
 
        self._real_inter = None
3284
 
 
3285
 
    @staticmethod
3286
 
    def is_compatible(source, target):
3287
 
        if isinstance(target, remote.RemoteRepository):
3288
 
            return True
3289
 
        return False
3290
 
 
3291
 
    def _ensure_real_inter(self):
3292
 
        if self._real_inter is None:
3293
 
            self.target._ensure_real()
3294
 
            real_target = self.target._real_repository
3295
 
            self._real_inter = InterRepository.get(self.source, real_target)
3296
 
            # Make _real_inter use the RemoteRepository for get_parent_map
3297
 
            self._real_inter.target_get_graph = self.target.get_graph
3298
 
            self._real_inter.target_get_parent_map = self.target.get_parent_map
3299
 
 
3300
 
    def copy_content(self, revision_id=None):
3301
 
        self._ensure_real_inter()
3302
 
        self._real_inter.copy_content(revision_id=revision_id)
3303
 
 
3304
 
    def fetch(self, revision_id=None, pb=None, find_ghosts=False,
3305
 
            fetch_spec=None):
3306
 
        self._ensure_real_inter()
3307
 
        return self._real_inter.fetch(revision_id=revision_id, pb=pb,
3308
 
            find_ghosts=find_ghosts, fetch_spec=fetch_spec)
3309
 
 
3310
 
    @classmethod
3311
 
    def _get_repo_format_to_test(self):
3312
 
        return None
3313
 
 
3314
 
 
3315
 
class InterRemoteToOther(InterRepository):
3316
 
 
3317
 
    def __init__(self, source, target):
3318
 
        InterRepository.__init__(self, source, target)
3319
 
        self._real_inter = None
3320
 
 
3321
 
    @staticmethod
3322
 
    def is_compatible(source, target):
3323
 
        if not isinstance(source, remote.RemoteRepository):
3324
 
            return False
3325
 
        return InterRepository._same_model(source, target)
3326
 
 
3327
 
    def _ensure_real_inter(self):
3328
 
        if self._real_inter is None:
3329
 
            self.source._ensure_real()
3330
 
            real_source = self.source._real_repository
3331
 
            self._real_inter = InterRepository.get(real_source, self.target)
3332
 
 
3333
 
    @needs_write_lock
3334
 
    def fetch(self, revision_id=None, pb=None, find_ghosts=False,
3335
 
            fetch_spec=None):
3336
 
        """See InterRepository.fetch()."""
3337
 
        # Always fetch using the generic streaming fetch code, to allow
3338
 
        # streaming fetching from remote servers.
3339
 
        from bzrlib.fetch import RepoFetcher
3340
 
        fetcher = RepoFetcher(self.target, self.source, revision_id,
3341
 
                pb, find_ghosts, fetch_spec=fetch_spec)
3342
 
 
3343
 
    def copy_content(self, revision_id=None):
3344
 
        self._ensure_real_inter()
3345
 
        self._real_inter.copy_content(revision_id=revision_id)
3346
 
 
3347
 
    @classmethod
3348
 
    def _get_repo_format_to_test(self):
3349
 
        return None
3350
 
 
3351
 
 
3352
 
 
3353
 
class InterPackToRemotePack(InterPackRepo):
3354
 
    """A specialisation of InterPackRepo for a target that is a
3355
 
    RemoteRepository.
3356
 
 
3357
 
    This will use the get_parent_map RPC rather than plain readvs, and also
3358
 
    uses an RPC for autopacking.
3359
 
    """
3360
 
 
3361
 
    _walk_to_common_revisions_batch_size = 50
3362
 
 
3363
 
    @staticmethod
3364
 
    def is_compatible(source, target):
3365
 
        from bzrlib.repofmt.pack_repo import RepositoryFormatPack
3366
 
        if isinstance(source._format, RepositoryFormatPack):
3367
 
            if isinstance(target, remote.RemoteRepository):
3368
 
                target._format._ensure_real()
3369
 
                if isinstance(target._format._custom_format,
3370
 
                              RepositoryFormatPack):
3371
 
                    if InterRepository._same_model(source, target):
3372
 
                        return True
3373
 
        return False
3374
 
 
3375
 
    def _autopack(self):
3376
 
        self.target.autopack()
3377
 
 
3378
 
    @needs_write_lock
3379
 
    def fetch(self, revision_id=None, pb=None, find_ghosts=False,
3380
 
            fetch_spec=None):
3381
 
        """See InterRepository.fetch()."""
3382
 
        # Always fetch using the generic streaming fetch code, to allow
3383
 
        # streaming fetching into remote servers.
3384
 
        from bzrlib.fetch import RepoFetcher
3385
 
        fetcher = RepoFetcher(self.target, self.source, revision_id,
3386
 
                              pb, find_ghosts, fetch_spec=fetch_spec)
3387
 
 
3388
 
    def _get_target_pack_collection(self):
3389
 
        return self.target._real_repository._pack_collection
3390
 
 
3391
 
    @classmethod
3392
 
    def _get_repo_format_to_test(self):
3393
 
        return None
3394
 
 
3395
 
 
3396
3286
InterRepository.register_optimiser(InterDifferingSerializer)
3397
3287
InterRepository.register_optimiser(InterSameDataRepository)
3398
3288
InterRepository.register_optimiser(InterWeaveRepo)
3399
3289
InterRepository.register_optimiser(InterKnitRepo)
3400
3290
InterRepository.register_optimiser(InterPackRepo)
3401
 
InterRepository.register_optimiser(InterOtherToRemote)
3402
 
InterRepository.register_optimiser(InterRemoteToOther)
3403
 
InterRepository.register_optimiser(InterPackToRemotePack)
3404
3291
 
3405
3292
 
3406
3293
class CopyConverter(object):
3487
3374
 
3488
3375
class _VersionedFileChecker(object):
3489
3376
 
3490
 
    def __init__(self, repository):
 
3377
    def __init__(self, repository, text_key_references=None):
3491
3378
        self.repository = repository
3492
 
        self.text_index = self.repository._generate_text_key_index()
 
3379
        self.text_index = self.repository._generate_text_key_index(
 
3380
            text_key_references=text_key_references)
3493
3381
 
3494
3382
    def calculate_file_version_parents(self, text_key):
3495
3383
        """Calculate the correct parents for a file version according to