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

  • Committer: John Ferlito
  • Date: 2009-09-02 04:31:45 UTC
  • mto: (4665.7.1 serve-init)
  • mto: This revision was merged to the branch mainline in revision 4913.
  • Revision ID: johnf@inodes.org-20090902043145-gxdsfw03ilcwbyn5
Add a debian init script for bzr --serve

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005-2011 Canonical Ltd
 
1
# Copyright (C) 2005, 2006, 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
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
 
17
18
"""Copying of history from one branch to another.
18
19
 
19
20
The basic plan is that every branch knows the history of everything
24
25
 
25
26
import operator
26
27
 
27
 
from ..lazy_import import lazy_import
 
28
from bzrlib.lazy_import import lazy_import
28
29
lazy_import(globals(), """
29
 
from breezy import (
 
30
from bzrlib import (
30
31
    tsort,
31
 
    )
32
 
from breezy.bzr import (
33
32
    versionedfile,
34
 
    vf_search,
35
33
    )
36
34
""")
37
 
from .. import (
 
35
import bzrlib
 
36
from bzrlib import (
38
37
    errors,
39
 
    ui,
 
38
    symbol_versioning,
40
39
    )
41
 
from ..i18n import gettext
42
 
from ..revision import NULL_REVISION
43
 
from ..trace import mutter
 
40
from bzrlib.revision import NULL_REVISION
 
41
from bzrlib.trace import mutter
 
42
import bzrlib.ui
44
43
 
45
44
 
46
45
class RepoFetcher(object):
51
50
    """
52
51
 
53
52
    def __init__(self, to_repository, from_repository, last_revision=None,
54
 
                 find_ghosts=True, fetch_spec=None):
 
53
        pb=None, find_ghosts=True, fetch_spec=None):
55
54
        """Create a repo fetcher.
56
55
 
57
56
        :param last_revision: If set, try to limit to the data this revision
58
57
            references.
59
 
        :param fetch_spec: A SearchResult specifying which revisions to fetch.
60
 
            If set, this overrides last_revision.
61
58
        :param find_ghosts: If True search the entire history for ghosts.
 
59
        :param pb: ProgressBar object to use; deprecated and ignored.
 
60
            This method will just create one on top of the stack.
62
61
        """
 
62
        if pb is not None:
 
63
            symbol_versioning.warn(
 
64
                symbol_versioning.deprecated_in((1, 14, 0))
 
65
                % "pb parameter to RepoFetcher.__init__")
 
66
            # and for simplicity it is in fact ignored
63
67
        # repository.fetch has the responsibility for short-circuiting
64
68
        # attempts to copy between a repository and itself.
65
69
        self.to_repository = to_repository
69
73
        self._last_revision = last_revision
70
74
        self._fetch_spec = fetch_spec
71
75
        self.find_ghosts = find_ghosts
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))
 
76
        self.from_repository.lock_read()
 
77
        mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
 
78
               self.from_repository, self.from_repository._format,
 
79
               self.to_repository, self.to_repository._format)
 
80
        try:
76
81
            self.__fetch()
 
82
        finally:
 
83
            self.from_repository.unlock()
77
84
 
78
85
    def __fetch(self):
79
86
        """Primary worker function.
89
96
        # assert not missing
90
97
        self.count_total = 0
91
98
        self.file_ids_names = {}
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():
 
99
        pb = bzrlib.ui.ui_factory.nested_progress_bar()
 
100
        pb.show_pct = pb.show_count = False
 
101
        try:
 
102
            pb.update("Finding revisions", 0, 2)
 
103
            search = self._revids_to_fetch()
 
104
            if search is None:
98
105
                return
99
 
            pb.update(gettext("Fetching revisions"), 1, 2)
100
 
            self._fetch_everything_for_search(search_result)
 
106
            pb.update("Fetching revisions", 1, 2)
 
107
            self._fetch_everything_for_search(search)
 
108
        finally:
 
109
            pb.finished()
101
110
 
102
111
    def _fetch_everything_for_search(self, search):
103
112
        """Fetch all data for the given set of revisions."""
110
119
        # moment, so that it can feed the progress information back to this
111
120
        # function?
112
121
        if (self.from_repository._format.rich_root_data and
113
 
                not self.to_repository._format.rich_root_data):
 
122
            not self.to_repository._format.rich_root_data):
114
123
            raise errors.IncompatibleRepositories(
115
124
                self.from_repository, self.to_repository,
116
125
                "different rich-root support")
117
 
        with ui.ui_factory.nested_progress_bar() as pb:
 
126
        pb = bzrlib.ui.ui_factory.nested_progress_bar()
 
127
        try:
118
128
            pb.update("Get stream source")
119
129
            source = self.from_repository._get_source(
120
130
                self.to_repository._format)
123
133
            pb.update("Inserting stream")
124
134
            resume_tokens, missing_keys = self.sink.insert_stream(
125
135
                stream, from_format, [])
 
136
            if self.to_repository._fallback_repositories:
 
137
                missing_keys.update(
 
138
                    self._parent_inventories(search.get_keys()))
126
139
            if missing_keys:
127
140
                pb.update("Missing keys")
128
141
                stream = source.get_stream_for_missing_keys(missing_keys)
139
152
                        resume_tokens,))
140
153
            pb.update("Finishing stream")
141
154
            self.sink.finished()
 
155
        finally:
 
156
            pb.finished()
142
157
 
143
158
    def _revids_to_fetch(self):
144
159
        """Determines the exact revisions needed from self.from_repository to
145
160
        install self._last_revision in self.to_repository.
146
161
 
147
 
        :returns: A SearchResult of some sort.  (Possibly a
148
 
            PendingAncestryResult, EmptySearchResult, etc.)
 
162
        If no revisions need to be fetched, then this just returns None.
149
163
        """
150
164
        if self._fetch_spec is not None:
151
 
            # The fetch spec is already a concrete search result.
152
165
            return self._fetch_spec
153
 
        elif self._last_revision == NULL_REVISION:
154
 
            # fetch_spec is None + last_revision is null => empty fetch.
 
166
        mutter('fetch up to rev {%s}', self._last_revision)
 
167
        if self._last_revision is NULL_REVISION:
155
168
            # explicit limit of no revisions needed
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()
 
169
            return None
 
170
        return self.to_repository.search_missing_revision_ids(
 
171
            self.from_repository, self._last_revision,
 
172
            find_ghosts=self.find_ghosts)
 
173
 
 
174
    def _parent_inventories(self, revision_ids):
 
175
        # Find all the parent revisions referenced by the stream, but
 
176
        # not present in the stream, and make sure we send their
 
177
        # inventories.
 
178
        parent_maps = self.to_repository.get_parent_map(revision_ids)
 
179
        parents = set()
 
180
        map(parents.update, parent_maps.itervalues())
 
181
        parents.discard(NULL_REVISION)
 
182
        parents.difference_update(revision_ids)
 
183
        missing_keys = set(('inventories', rev_id) for rev_id in parents)
 
184
        return missing_keys
166
185
 
167
186
 
168
187
class Inter1and2Helper(object):
171
190
    This is for use by fetchers and converters.
172
191
    """
173
192
 
174
 
    # This is a class variable so that the test suite can override it.
175
 
    known_graph_threshold = 100
176
 
 
177
193
    def __init__(self, source):
178
194
        """Constructor.
179
195
 
195
211
        revs = list(revs)
196
212
        while revs:
197
213
            for tree in self.source.revision_trees(revs[:100]):
198
 
                if tree.root_inventory.revision_id is None:
199
 
                    tree.root_inventory.revision_id = tree.get_revision_id()
 
214
                if tree.inventory.revision_id is None:
 
215
                    tree.inventory.revision_id = tree.get_revision_id()
200
216
                yield tree
201
217
            revs = revs[100:]
202
218
 
203
219
    def _find_root_ids(self, revs, parent_map, graph):
204
220
        revision_root = {}
205
221
        for tree in self.iter_rev_trees(revs):
206
 
            root_id = tree.path2id('')
207
 
            revision_id = tree.get_file_revision(u'')
 
222
            revision_id = tree.inventory.root.revision
 
223
            root_id = tree.get_root_id()
208
224
            revision_root[revision_id] = root_id
209
225
        # Find out which parents we don't already know root ids for
210
 
        parents = set(parent_map.values())
211
 
        parents.difference_update(revision_root)
212
 
        parents.discard(NULL_REVISION)
 
226
        parents = set()
 
227
        for revision_parents in parent_map.itervalues():
 
228
            parents.update(revision_parents)
 
229
        parents.difference_update(revision_root.keys() + [NULL_REVISION])
213
230
        # Limit to revisions present in the versionedfile
214
 
        parents = graph.get_parent_map(parents)
 
231
        parents = graph.get_parent_map(parents).keys()
215
232
        for tree in self.iter_rev_trees(parents):
216
 
            root_id = tree.path2id('')
 
233
            root_id = tree.get_root_id()
217
234
            revision_root[tree.get_revision_id()] = root_id
218
235
        return revision_root
219
236
 
227
244
        rev_order = tsort.topo_sort(parent_map)
228
245
        rev_id_to_root_id = self._find_root_ids(revs, parent_map, graph)
229
246
        root_id_order = [(rev_id_to_root_id[rev_id], rev_id) for rev_id in
230
 
                         rev_order]
 
247
            rev_order]
231
248
        # Guaranteed stable, this groups all the file id operations together
232
249
        # retaining topological order within the revisions of a file id.
233
250
        # File id splits and joins would invalidate this, but they don't exist
234
251
        # yet, and are unlikely to in non-rich-root environments anyway.
235
252
        root_id_order.sort(key=operator.itemgetter(0))
236
253
        # Create a record stream containing the roots to create.
237
 
        if len(revs) > self.known_graph_threshold:
238
 
            graph = self.source.get_known_graph_ancestry(revs)
 
254
        from bzrlib.graph import FrozenHeadsCache
 
255
        graph = FrozenHeadsCache(graph)
239
256
        new_roots_stream = _new_root_data_stream(
240
257
            root_id_order, rev_id_to_root_id, parent_map, self.source, graph)
241
258
        return [('texts', new_roots_stream)]
242
259
 
243
260
 
244
261
def _new_root_data_stream(
245
 
        root_keys_to_create, rev_id_to_root_id_map, parent_map, repo, graph=None):
 
262
    root_keys_to_create, rev_id_to_root_id_map, parent_map, repo, graph=None):
246
263
    """Generate a texts substream of synthesised root entries.
247
264
 
248
265
    Used in fetches that do rich-root upgrades.
249
 
 
 
266
    
250
267
    :param root_keys_to_create: iterable of (root_id, rev_id) pairs describing
251
268
        the root entries to create.
252
269
    :param rev_id_to_root_id_map: dict of known rev_id -> root_id mappings for
260
277
        root_id, rev_id = root_key
261
278
        parent_keys = _parent_keys_for_root_version(
262
279
            root_id, rev_id, rev_id_to_root_id_map, parent_map, repo, graph)
263
 
        yield versionedfile.ChunkedContentFactory(
264
 
            root_key, parent_keys, None, [])
 
280
        yield versionedfile.FulltextContentFactory(
 
281
            root_key, parent_keys, None, '')
265
282
 
266
283
 
267
284
def _parent_keys_for_root_version(
268
 
        root_id, rev_id, rev_id_to_root_id_map, parent_map, repo, graph=None):
 
285
    root_id, rev_id, rev_id_to_root_id_map, parent_map, repo, graph=None):
269
286
    """Get the parent keys for a given root id.
270
 
 
 
287
    
271
288
    A helper function for _new_root_data_stream.
272
289
    """
273
290
    # Include direct parents of the revision, but only if they used the same
288
305
                # But set parent_root_id to None since we don't really know
289
306
                parent_root_id = None
290
307
            else:
291
 
                parent_root_id = tree.path2id('')
 
308
                parent_root_id = tree.get_root_id()
292
309
            rev_id_to_root_id_map[parent_id] = None
293
310
            # XXX: why not:
294
311
            #   rev_id_to_root_id_map[parent_id] = parent_root_id
310
327
                pass
311
328
            else:
312
329
                try:
313
 
                    parent_ids.append(
314
 
                        tree.get_file_revision(
315
 
                            tree.id2path(root_id, recurse='none')))
 
330
                    parent_ids.append(tree.inventory[root_id].revision)
316
331
                except errors.NoSuchId:
317
332
                    # not in the tree
318
333
                    pass
326
341
            selected_ids.append(parent_id)
327
342
    parent_keys = [(root_id, parent_id) for parent_id in selected_ids]
328
343
    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()