/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 breezy/bzr/fetch.py

  • Committer: Jelmer Vernooij
  • Date: 2020-05-24 00:42:36 UTC
  • mto: This revision was merged to the branch mainline in revision 7505.
  • Revision ID: jelmer@jelmer.uk-20200524004236-jdj6obo4k5lznqw2
Cleanup Windows functions.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005-2010 Canonical Ltd
 
1
# Copyright (C) 2005-2011 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
 
 
18
17
"""Copying of history from one branch to another.
19
18
 
20
19
The basic plan is that every branch knows the history of everything
25
24
 
26
25
import operator
27
26
 
28
 
from bzrlib.lazy_import import lazy_import
 
27
from ..lazy_import import lazy_import
29
28
lazy_import(globals(), """
30
 
from bzrlib import (
 
29
from breezy import (
31
30
    tsort,
 
31
    )
 
32
from breezy.bzr import (
32
33
    versionedfile,
 
34
    vf_search,
33
35
    )
34
36
""")
35
 
import bzrlib
36
 
from bzrlib import (
 
37
from .. import (
37
38
    errors,
38
39
    ui,
39
40
    )
40
 
from bzrlib.revision import NULL_REVISION
41
 
from bzrlib.trace import mutter
 
41
from ..i18n import gettext
 
42
from ..revision import NULL_REVISION
 
43
from ..trace import mutter
42
44
 
43
45
 
44
46
class RepoFetcher(object):
49
51
    """
50
52
 
51
53
    def __init__(self, to_repository, from_repository, last_revision=None,
52
 
        find_ghosts=True, fetch_spec=None):
 
54
                 find_ghosts=True, fetch_spec=None):
53
55
        """Create a repo fetcher.
54
56
 
55
57
        :param last_revision: If set, try to limit to the data this revision
56
58
            references.
 
59
        :param fetch_spec: A SearchResult specifying which revisions to fetch.
 
60
            If set, this overrides last_revision.
57
61
        :param find_ghosts: If True search the entire history for ghosts.
58
62
        """
59
63
        # repository.fetch has the responsibility for short-circuiting
65
69
        self._last_revision = last_revision
66
70
        self._fetch_spec = fetch_spec
67
71
        self.find_ghosts = find_ghosts
68
 
        self.from_repository.lock_read()
69
 
        mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
70
 
               self.from_repository, self.from_repository._format,
71
 
               self.to_repository, self.to_repository._format)
72
 
        try:
 
72
        with self.from_repository.lock_read():
 
73
            mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
 
74
                   str(self.from_repository), str(self.from_repository._format),
 
75
                   str(self.to_repository), str(self.to_repository._format))
73
76
            self.__fetch()
74
 
        finally:
75
 
            self.from_repository.unlock()
76
77
 
77
78
    def __fetch(self):
78
79
        """Primary worker function.
88
89
        # assert not missing
89
90
        self.count_total = 0
90
91
        self.file_ids_names = {}
91
 
        pb = ui.ui_factory.nested_progress_bar()
92
 
        pb.show_pct = pb.show_count = False
93
 
        try:
94
 
            pb.update("Finding revisions", 0, 2)
95
 
            search = self._revids_to_fetch()
96
 
            if search is None:
 
92
        with ui.ui_factory.nested_progress_bar() as pb:
 
93
            pb.show_pct = pb.show_count = False
 
94
            pb.update(gettext("Finding revisions"), 0, 2)
 
95
            search_result = self._revids_to_fetch()
 
96
            mutter('fetching: %s', str(search_result))
 
97
            if search_result.is_empty():
97
98
                return
98
 
            pb.update("Fetching revisions", 1, 2)
99
 
            self._fetch_everything_for_search(search)
100
 
        finally:
101
 
            pb.finished()
 
99
            pb.update(gettext("Fetching revisions"), 1, 2)
 
100
            self._fetch_everything_for_search(search_result)
102
101
 
103
102
    def _fetch_everything_for_search(self, search):
104
103
        """Fetch all data for the given set of revisions."""
111
110
        # moment, so that it can feed the progress information back to this
112
111
        # function?
113
112
        if (self.from_repository._format.rich_root_data and
114
 
            not self.to_repository._format.rich_root_data):
 
113
                not self.to_repository._format.rich_root_data):
115
114
            raise errors.IncompatibleRepositories(
116
115
                self.from_repository, self.to_repository,
117
116
                "different rich-root support")
118
 
        pb = ui.ui_factory.nested_progress_bar()
119
 
        try:
 
117
        with ui.ui_factory.nested_progress_bar() as pb:
120
118
            pb.update("Get stream source")
121
119
            source = self.from_repository._get_source(
122
120
                self.to_repository._format)
125
123
            pb.update("Inserting stream")
126
124
            resume_tokens, missing_keys = self.sink.insert_stream(
127
125
                stream, from_format, [])
128
 
            if self.to_repository._fallback_repositories:
129
 
                missing_keys.update(
130
 
                    self._parent_inventories(search.get_keys()))
131
126
            if missing_keys:
132
127
                pb.update("Missing keys")
133
128
                stream = source.get_stream_for_missing_keys(missing_keys)
144
139
                        resume_tokens,))
145
140
            pb.update("Finishing stream")
146
141
            self.sink.finished()
147
 
        finally:
148
 
            pb.finished()
149
142
 
150
143
    def _revids_to_fetch(self):
151
144
        """Determines the exact revisions needed from self.from_repository to
152
145
        install self._last_revision in self.to_repository.
153
146
 
154
 
        If no revisions need to be fetched, then this just returns None.
 
147
        :returns: A SearchResult of some sort.  (Possibly a
 
148
            PendingAncestryResult, EmptySearchResult, etc.)
155
149
        """
156
150
        if self._fetch_spec is not None:
 
151
            # The fetch spec is already a concrete search result.
157
152
            return self._fetch_spec
158
 
        mutter('fetch up to rev {%s}', self._last_revision)
159
 
        if self._last_revision is NULL_REVISION:
 
153
        elif self._last_revision == NULL_REVISION:
 
154
            # fetch_spec is None + last_revision is null => empty fetch.
160
155
            # explicit limit of no revisions needed
161
 
            return None
162
 
        return self.to_repository.search_missing_revision_ids(
163
 
            self.from_repository, self._last_revision,
164
 
            find_ghosts=self.find_ghosts)
165
 
 
166
 
    def _parent_inventories(self, revision_ids):
167
 
        # Find all the parent revisions referenced by the stream, but
168
 
        # not present in the stream, and make sure we send their
169
 
        # inventories.
170
 
        parent_maps = self.to_repository.get_parent_map(revision_ids)
171
 
        parents = set()
172
 
        map(parents.update, parent_maps.itervalues())
173
 
        parents.discard(NULL_REVISION)
174
 
        parents.difference_update(revision_ids)
175
 
        missing_keys = set(('inventories', rev_id) for rev_id in parents)
176
 
        return missing_keys
 
156
            return vf_search.EmptySearchResult()
 
157
        elif self._last_revision is not None:
 
158
            return vf_search.NotInOtherForRevs(self.to_repository,
 
159
                                               self.from_repository, [
 
160
                                                   self._last_revision],
 
161
                                               find_ghosts=self.find_ghosts).execute()
 
162
        else:  # self._last_revision is None:
 
163
            return vf_search.EverythingNotInOther(self.to_repository,
 
164
                                                  self.from_repository,
 
165
                                                  find_ghosts=self.find_ghosts).execute()
177
166
 
178
167
 
179
168
class Inter1and2Helper(object):
182
171
    This is for use by fetchers and converters.
183
172
    """
184
173
 
 
174
    # This is a class variable so that the test suite can override it.
 
175
    known_graph_threshold = 100
 
176
 
185
177
    def __init__(self, source):
186
178
        """Constructor.
187
179
 
203
195
        revs = list(revs)
204
196
        while revs:
205
197
            for tree in self.source.revision_trees(revs[:100]):
206
 
                if tree.inventory.revision_id is None:
207
 
                    tree.inventory.revision_id = tree.get_revision_id()
 
198
                if tree.root_inventory.revision_id is None:
 
199
                    tree.root_inventory.revision_id = tree.get_revision_id()
208
200
                yield tree
209
201
            revs = revs[100:]
210
202
 
211
203
    def _find_root_ids(self, revs, parent_map, graph):
212
204
        revision_root = {}
213
205
        for tree in self.iter_rev_trees(revs):
214
 
            revision_id = tree.inventory.root.revision
215
 
            root_id = tree.get_root_id()
 
206
            root_id = tree.path2id('')
 
207
            revision_id = tree.get_file_revision(u'')
216
208
            revision_root[revision_id] = root_id
217
209
        # Find out which parents we don't already know root ids for
218
 
        parents = set()
219
 
        for revision_parents in parent_map.itervalues():
220
 
            parents.update(revision_parents)
221
 
        parents.difference_update(revision_root.keys() + [NULL_REVISION])
 
210
        parents = set(parent_map.values())
 
211
        parents.difference_update(revision_root)
 
212
        parents.discard(NULL_REVISION)
222
213
        # Limit to revisions present in the versionedfile
223
 
        parents = graph.get_parent_map(parents).keys()
 
214
        parents = graph.get_parent_map(parents)
224
215
        for tree in self.iter_rev_trees(parents):
225
 
            root_id = tree.get_root_id()
 
216
            root_id = tree.path2id('')
226
217
            revision_root[tree.get_revision_id()] = root_id
227
218
        return revision_root
228
219
 
236
227
        rev_order = tsort.topo_sort(parent_map)
237
228
        rev_id_to_root_id = self._find_root_ids(revs, parent_map, graph)
238
229
        root_id_order = [(rev_id_to_root_id[rev_id], rev_id) for rev_id in
239
 
            rev_order]
 
230
                         rev_order]
240
231
        # Guaranteed stable, this groups all the file id operations together
241
232
        # retaining topological order within the revisions of a file id.
242
233
        # File id splits and joins would invalidate this, but they don't exist
243
234
        # yet, and are unlikely to in non-rich-root environments anyway.
244
235
        root_id_order.sort(key=operator.itemgetter(0))
245
236
        # Create a record stream containing the roots to create.
246
 
        if len(revs) > 100:
247
 
            # XXX: not covered by tests, should have a flag to always run
248
 
            # this. -- mbp 20100129
249
 
            graph = self.source_repo.get_known_graph_ancestry(revs)
 
237
        if len(revs) > self.known_graph_threshold:
 
238
            graph = self.source.get_known_graph_ancestry(revs)
250
239
        new_roots_stream = _new_root_data_stream(
251
240
            root_id_order, rev_id_to_root_id, parent_map, self.source, graph)
252
241
        return [('texts', new_roots_stream)]
253
242
 
254
243
 
255
 
def _get_rich_root_heads_graph(source_repo, revision_ids):
256
 
    """Get a Graph object suitable for asking heads() for new rich roots."""
257
 
    return 
258
 
 
259
 
 
260
244
def _new_root_data_stream(
261
 
    root_keys_to_create, rev_id_to_root_id_map, parent_map, repo, graph=None):
 
245
        root_keys_to_create, rev_id_to_root_id_map, parent_map, repo, graph=None):
262
246
    """Generate a texts substream of synthesised root entries.
263
247
 
264
248
    Used in fetches that do rich-root upgrades.
265
 
    
 
249
 
266
250
    :param root_keys_to_create: iterable of (root_id, rev_id) pairs describing
267
251
        the root entries to create.
268
252
    :param rev_id_to_root_id_map: dict of known rev_id -> root_id mappings for
276
260
        root_id, rev_id = root_key
277
261
        parent_keys = _parent_keys_for_root_version(
278
262
            root_id, rev_id, rev_id_to_root_id_map, parent_map, repo, graph)
279
 
        yield versionedfile.FulltextContentFactory(
280
 
            root_key, parent_keys, None, '')
 
263
        yield versionedfile.ChunkedContentFactory(
 
264
            root_key, parent_keys, None, [])
281
265
 
282
266
 
283
267
def _parent_keys_for_root_version(
284
 
    root_id, rev_id, rev_id_to_root_id_map, parent_map, repo, graph=None):
 
268
        root_id, rev_id, rev_id_to_root_id_map, parent_map, repo, graph=None):
285
269
    """Get the parent keys for a given root id.
286
 
    
 
270
 
287
271
    A helper function for _new_root_data_stream.
288
272
    """
289
273
    # Include direct parents of the revision, but only if they used the same
304
288
                # But set parent_root_id to None since we don't really know
305
289
                parent_root_id = None
306
290
            else:
307
 
                parent_root_id = tree.get_root_id()
 
291
                parent_root_id = tree.path2id('')
308
292
            rev_id_to_root_id_map[parent_id] = None
309
293
            # XXX: why not:
310
294
            #   rev_id_to_root_id_map[parent_id] = parent_root_id
326
310
                pass
327
311
            else:
328
312
                try:
329
 
                    parent_ids.append(tree.inventory[root_id].revision)
 
313
                    parent_ids.append(
 
314
                        tree.get_file_revision(
 
315
                            tree.id2path(root_id, recurse='none')))
330
316
                except errors.NoSuchId:
331
317
                    # not in the tree
332
318
                    pass
340
326
            selected_ids.append(parent_id)
341
327
    parent_keys = [(root_id, parent_id) for parent_id in selected_ids]
342
328
    return parent_keys
 
329
 
 
330
 
 
331
class TargetRepoKinds(object):
 
332
    """An enum-like set of constants.
 
333
 
 
334
    They are the possible values of FetchSpecFactory.target_repo_kinds.
 
335
    """
 
336
 
 
337
    PREEXISTING = 'preexisting'
 
338
    STACKED = 'stacked'
 
339
    EMPTY = 'empty'
 
340
 
 
341
 
 
342
class FetchSpecFactory(object):
 
343
    """A helper for building the best fetch spec for a sprout call.
 
344
 
 
345
    Factors that go into determining the sort of fetch to perform:
 
346
     * did the caller specify any revision IDs?
 
347
     * did the caller specify a source branch (need to fetch its
 
348
       heads_to_fetch(), usually the tip + tags)
 
349
     * is there an existing target repo (don't need to refetch revs it
 
350
       already has)
 
351
     * target is stacked?  (similar to pre-existing target repo: even if
 
352
       the target itself is new don't want to refetch existing revs)
 
353
 
 
354
    :ivar source_branch: the source branch if one specified, else None.
 
355
    :ivar source_branch_stop_revision_id: fetch up to this revision of
 
356
        source_branch, rather than its tip.
 
357
    :ivar source_repo: the source repository if one found, else None.
 
358
    :ivar target_repo: the target repository acquired by sprout.
 
359
    :ivar target_repo_kind: one of the TargetRepoKinds constants.
 
360
    """
 
361
 
 
362
    def __init__(self):
 
363
        self._explicit_rev_ids = set()
 
364
        self.source_branch = None
 
365
        self.source_branch_stop_revision_id = None
 
366
        self.source_repo = None
 
367
        self.target_repo = None
 
368
        self.target_repo_kind = None
 
369
        self.limit = None
 
370
 
 
371
    def add_revision_ids(self, revision_ids):
 
372
        """Add revision_ids to the set of revision_ids to be fetched."""
 
373
        self._explicit_rev_ids.update(revision_ids)
 
374
 
 
375
    def make_fetch_spec(self):
 
376
        """Build a SearchResult or PendingAncestryResult or etc."""
 
377
        if self.target_repo_kind is None or self.source_repo is None:
 
378
            raise AssertionError(
 
379
                'Incomplete FetchSpecFactory: %r' % (self.__dict__,))
 
380
        if len(self._explicit_rev_ids) == 0 and self.source_branch is None:
 
381
            if self.limit is not None:
 
382
                raise NotImplementedError(
 
383
                    "limit is only supported with a source branch set")
 
384
            # Caller hasn't specified any revisions or source branch
 
385
            if self.target_repo_kind == TargetRepoKinds.EMPTY:
 
386
                return vf_search.EverythingResult(self.source_repo)
 
387
            else:
 
388
                # We want everything not already in the target (or target's
 
389
                # fallbacks).
 
390
                return vf_search.EverythingNotInOther(
 
391
                    self.target_repo, self.source_repo).execute()
 
392
        heads_to_fetch = set(self._explicit_rev_ids)
 
393
        if self.source_branch is not None:
 
394
            must_fetch, if_present_fetch = self.source_branch.heads_to_fetch()
 
395
            if self.source_branch_stop_revision_id is not None:
 
396
                # Replace the tip rev from must_fetch with the stop revision
 
397
                # XXX: this might be wrong if the tip rev is also in the
 
398
                # must_fetch set for other reasons (e.g. it's the tip of
 
399
                # multiple loom threads?), but then it's pretty unclear what it
 
400
                # should mean to specify a stop_revision in that case anyway.
 
401
                must_fetch.discard(self.source_branch.last_revision())
 
402
                must_fetch.add(self.source_branch_stop_revision_id)
 
403
            heads_to_fetch.update(must_fetch)
 
404
        else:
 
405
            if_present_fetch = set()
 
406
        if self.target_repo_kind == TargetRepoKinds.EMPTY:
 
407
            # PendingAncestryResult does not raise errors if a requested head
 
408
            # is absent.  Ideally it would support the
 
409
            # required_ids/if_present_ids distinction, but in practice
 
410
            # heads_to_fetch will almost certainly be present so this doesn't
 
411
            # matter much.
 
412
            all_heads = heads_to_fetch.union(if_present_fetch)
 
413
            ret = vf_search.PendingAncestryResult(all_heads, self.source_repo)
 
414
            if self.limit is not None:
 
415
                graph = self.source_repo.get_graph()
 
416
                topo_order = list(graph.iter_topo_order(ret.get_keys()))
 
417
                result_set = topo_order[:self.limit]
 
418
                ret = self.source_repo.revision_ids_to_search_result(
 
419
                    result_set)
 
420
            return ret
 
421
        else:
 
422
            return vf_search.NotInOtherForRevs(self.target_repo, self.source_repo,
 
423
                                               required_ids=heads_to_fetch, if_present_ids=if_present_fetch,
 
424
                                               limit=self.limit).execute()