~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/fetch.py

  • Committer: Martin Pool
  • Date: 2008-03-16 08:25:21 UTC
  • mto: (3280.2.2 prepare-1.3)
  • mto: This revision was merged to the branch mainline in revision 3284.
  • Revision ID: mbp@sourcefrog.net-20080316082521-xmex8wq1uyj6cxyh
Fix doctest syntax

Show diffs side-by-side

added added

removed removed

Lines of Context:
33
33
 
34
34
import bzrlib
35
35
import bzrlib.errors as errors
36
 
from bzrlib.errors import (InstallFailed,
37
 
                           )
 
36
from bzrlib.errors import InstallFailed
38
37
from bzrlib.progress import ProgressPhase
39
 
from bzrlib.revision import NULL_REVISION
 
38
from bzrlib.revision import is_null, NULL_REVISION
40
39
from bzrlib.symbol_versioning import (deprecated_function,
41
40
        deprecated_method,
42
 
        zero_eight,
43
41
        )
44
42
from bzrlib.trace import mutter
45
43
import bzrlib.ui
46
44
 
 
45
from bzrlib.lazy_import import lazy_import
47
46
 
48
47
# TODO: Avoid repeatedly opening weaves so many times.
49
48
 
63
62
#   and add in all file versions
64
63
 
65
64
 
66
 
@deprecated_function(zero_eight)
67
 
def greedy_fetch(to_branch, from_branch, revision=None, pb=None):
68
 
    """Legacy API, please see branch.fetch(from_branch, last_revision, pb)."""
69
 
    f = Fetcher(to_branch, from_branch, revision, pb)
70
 
    return f.count_copied, f.failed_revisions
71
 
 
72
 
fetch = greedy_fetch
73
 
 
74
 
 
75
65
class RepoFetcher(object):
76
66
    """Pull revisions and texts from one repository to another.
77
67
 
81
71
    after running:
82
72
    count_copied -- number of revisions copied
83
73
 
84
 
    This should not be used directory, its essential a object to encapsulate
 
74
    This should not be used directly, it's essential a object to encapsulate
85
75
    the logic in InterRepository.fetch().
86
76
    """
87
 
    def __init__(self, to_repository, from_repository, last_revision=None, pb=None):
 
77
 
 
78
    def __init__(self, to_repository, from_repository, last_revision=None, pb=None,
 
79
        find_ghosts=True):
 
80
        """Create a repo fetcher.
 
81
 
 
82
        :param find_ghosts: If True search the entire history for ghosts.
 
83
        """
88
84
        # result variables.
89
85
        self.failed_revisions = []
90
86
        self.count_copied = 0
91
 
        if to_repository.control_files._transport.base == from_repository.control_files._transport.base:
92
 
            # check that last_revision is in 'from' and then return a no-operation.
93
 
            if last_revision not in (None, NULL_REVISION):
94
 
                from_repository.get_revision(last_revision)
95
 
            return
 
87
        if to_repository.has_same_location(from_repository):
 
88
            # repository.fetch should be taking care of this case.
 
89
            raise errors.BzrError('RepoFetcher run '
 
90
                    'between two objects at the same location: '
 
91
                    '%r and %r' % (to_repository, from_repository))
96
92
        self.to_repository = to_repository
97
93
        self.from_repository = from_repository
98
94
        # must not mutate self._last_revision as its potentially a shared instance
99
95
        self._last_revision = last_revision
 
96
        self.find_ghosts = find_ghosts
100
97
        if pb is None:
101
98
            self.pb = bzrlib.ui.ui_factory.nested_progress_bar()
102
99
            self.nested_pb = self.pb
107
104
        try:
108
105
            self.to_repository.lock_write()
109
106
            try:
110
 
                self.__fetch()
 
107
                self.to_repository.start_write_group()
 
108
                try:
 
109
                    self.__fetch()
 
110
                except:
 
111
                    self.to_repository.abort_write_group()
 
112
                    raise
 
113
                else:
 
114
                    self.to_repository.commit_write_group()
111
115
            finally:
112
116
                if self.nested_pb is not None:
113
117
                    self.nested_pb.finished()
122
126
        requested revisions, finally clearing the progress bar.
123
127
        """
124
128
        self.to_weaves = self.to_repository.weave_store
125
 
        self.to_control = self.to_repository.control_weaves
126
129
        self.from_weaves = self.from_repository.weave_store
127
 
        self.from_control = self.from_repository.control_weaves
128
130
        self.count_total = 0
129
131
        self.file_ids_names = {}
130
 
        pp = ProgressPhase('Fetch phase', 4, self.pb)
 
132
        pp = ProgressPhase('Transferring', 4, self.pb)
131
133
        try:
132
134
            pp.next_phase()
133
 
            revs = self._revids_to_fetch()
134
 
            # something to do ?
135
 
            if revs:
136
 
                pp.next_phase()
137
 
                self._fetch_weave_texts(revs)
138
 
                pp.next_phase()
139
 
                self._fetch_inventory_weave(revs)
140
 
                pp.next_phase()
141
 
                self._fetch_revision_texts(revs)
142
 
                self.count_copied += len(revs)
 
135
            search = self._revids_to_fetch()
 
136
            if search is None:
 
137
                return
 
138
            if getattr(self, '_fetch_everything_for_search', None) is not None:
 
139
                self._fetch_everything_for_search(search, pp)
 
140
            else:
 
141
                # backward compatibility
 
142
                self._fetch_everything_for_revisions(search.get_keys, pp)
143
143
        finally:
144
144
            self.pb.clear()
145
145
 
 
146
    def _fetch_everything_for_search(self, search, pp):
 
147
        """Fetch all data for the given set of revisions."""
 
148
        # The first phase is "file".  We pass the progress bar for it directly
 
149
        # into item_keys_introduced_by, which has more information about how
 
150
        # that phase is progressing than we do.  Progress updates for the other
 
151
        # phases are taken care of in this function.
 
152
        # XXX: there should be a clear owner of the progress reporting.  Perhaps
 
153
        # item_keys_introduced_by should have a richer API than it does at the
 
154
        # moment, so that it can feed the progress information back to this
 
155
        # function?
 
156
        phase = 'file'
 
157
        pb = bzrlib.ui.ui_factory.nested_progress_bar()
 
158
        try:
 
159
            revs = search.get_keys()
 
160
            data_to_fetch = self.from_repository.item_keys_introduced_by(revs, pb)
 
161
            for knit_kind, file_id, revisions in data_to_fetch:
 
162
                if knit_kind != phase:
 
163
                    phase = knit_kind
 
164
                    # Make a new progress bar for this phase
 
165
                    pb.finished()
 
166
                    pp.next_phase()
 
167
                    pb = bzrlib.ui.ui_factory.nested_progress_bar()
 
168
                if knit_kind == "file":
 
169
                    self._fetch_weave_text(file_id, revisions)
 
170
                elif knit_kind == "inventory":
 
171
                    # XXX:
 
172
                    # Once we've processed all the files, then we generate the root
 
173
                    # texts (if necessary), then we process the inventory.  It's a
 
174
                    # bit distasteful to have knit_kind == "inventory" mean this,
 
175
                    # perhaps it should happen on the first non-"file" knit, in case
 
176
                    # it's not always inventory?
 
177
                    self._generate_root_texts(revs)
 
178
                    self._fetch_inventory_weave(revs, pb)
 
179
                elif knit_kind == "signatures":
 
180
                    # Nothing to do here; this will be taken care of when
 
181
                    # _fetch_revision_texts happens.
 
182
                    pass
 
183
                elif knit_kind == "revisions":
 
184
                    self._fetch_revision_texts(revs, pb)
 
185
                else:
 
186
                    raise AssertionError("Unknown knit kind %r" % knit_kind)
 
187
        finally:
 
188
            if pb is not None:
 
189
                pb.finished()
 
190
        self.count_copied += len(revs)
 
191
        
146
192
    def _revids_to_fetch(self):
 
193
        """Determines the exact revisions needed from self.from_repository to
 
194
        install self._last_revision in self.to_repository.
 
195
 
 
196
        If no revisions need to be fetched, then this just returns None.
 
197
        """
147
198
        mutter('fetch up to rev {%s}', self._last_revision)
148
199
        if self._last_revision is NULL_REVISION:
149
200
            # explicit limit of no revisions needed
151
202
        if (self._last_revision is not None and
152
203
            self.to_repository.has_revision(self._last_revision)):
153
204
            return None
154
 
            
155
205
        try:
156
 
            return self.to_repository.missing_revision_ids(self.from_repository,
157
 
                                                           self._last_revision)
 
206
            return self.to_repository.search_missing_revision_ids(
 
207
                self.from_repository, self._last_revision,
 
208
                find_ghosts=self.find_ghosts)
158
209
        except errors.NoSuchRevision:
159
210
            raise InstallFailed([self._last_revision])
160
211
 
161
 
    def _fetch_weave_texts(self, revs):
162
 
        texts_pb = bzrlib.ui.ui_factory.nested_progress_bar()
163
 
        try:
164
 
            # fileids_altered_by_revision_ids requires reading the inventory
165
 
            # weave, we will need to read the inventory weave again when
166
 
            # all this is done, so enable caching for that specific weave
167
 
            inv_w = self.from_repository.get_inventory_weave()
168
 
            inv_w.enable_cache()
169
 
            file_ids = self.from_repository.fileids_altered_by_revision_ids(revs)
170
 
            count = 0
171
 
            num_file_ids = len(file_ids)
172
 
            for file_id, required_versions in file_ids.items():
173
 
                texts_pb.update("fetch texts", count, num_file_ids)
174
 
                count +=1
175
 
                to_weave = self.to_weaves.get_weave_or_empty(file_id,
176
 
                    self.to_repository.get_transaction())
177
 
                from_weave = self.from_weaves.get_weave(file_id,
178
 
                    self.from_repository.get_transaction())
179
 
                # we fetch all the texts, because texts do
180
 
                # not reference anything, and its cheap enough
181
 
                to_weave.join(from_weave, version_ids=required_versions)
182
 
                # we don't need *all* of this data anymore, but we dont know
183
 
                # what we do. This cache clearing will result in a new read 
184
 
                # of the knit data when we do the checkout, but probably we
185
 
                # want to emit the needed data on the fly rather than at the
186
 
                # end anyhow.
187
 
                # the from weave should know not to cache data being joined,
188
 
                # but its ok to ask it to clear.
189
 
                from_weave.clear_cache()
190
 
                to_weave.clear_cache()
191
 
        finally:
192
 
            texts_pb.finished()
193
 
 
194
 
    def _fetch_inventory_weave(self, revs):
195
 
        pb = bzrlib.ui.ui_factory.nested_progress_bar()
196
 
        try:
197
 
            pb.update("fetch inventory", 0, 2)
198
 
            to_weave = self.to_control.get_weave('inventory',
199
 
                    self.to_repository.get_transaction())
200
 
    
201
 
            child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
202
 
            try:
203
 
                # just merge, this is optimisable and its means we don't
204
 
                # copy unreferenced data such as not-needed inventories.
205
 
                pb.update("fetch inventory", 1, 3)
206
 
                from_weave = self.from_repository.get_inventory_weave()
207
 
                pb.update("fetch inventory", 2, 3)
208
 
                # we fetch only the referenced inventories because we do not
209
 
                # know for unselected inventories whether all their required
210
 
                # texts are present in the other repository - it could be
211
 
                # corrupt.
212
 
                to_weave.join(from_weave, pb=child_pb, msg='merge inventory',
213
 
                              version_ids=revs)
214
 
                from_weave.clear_cache()
215
 
            finally:
216
 
                child_pb.finished()
217
 
        finally:
218
 
            pb.finished()
 
212
    def _fetch_weave_text(self, file_id, required_versions):
 
213
        to_weave = self.to_weaves.get_weave_or_empty(file_id,
 
214
            self.to_repository.get_transaction())
 
215
        from_weave = self.from_weaves.get_weave(file_id,
 
216
            self.from_repository.get_transaction())
 
217
        # we fetch all the texts, because texts do
 
218
        # not reference anything, and its cheap enough
 
219
        to_weave.join(from_weave, version_ids=required_versions)
 
220
        # we don't need *all* of this data anymore, but we dont know
 
221
        # what we do. This cache clearing will result in a new read 
 
222
        # of the knit data when we do the checkout, but probably we
 
223
        # want to emit the needed data on the fly rather than at the
 
224
        # end anyhow.
 
225
        # the from weave should know not to cache data being joined,
 
226
        # but its ok to ask it to clear.
 
227
        from_weave.clear_cache()
 
228
        to_weave.clear_cache()
 
229
 
 
230
    def _fetch_inventory_weave(self, revs, pb):
 
231
        pb.update("fetch inventory", 0, 2)
 
232
        to_weave = self.to_repository.get_inventory_weave()
 
233
        child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
 
234
        try:
 
235
            # just merge, this is optimisable and its means we don't
 
236
            # copy unreferenced data such as not-needed inventories.
 
237
            pb.update("fetch inventory", 1, 3)
 
238
            from_weave = self.from_repository.get_inventory_weave()
 
239
            pb.update("fetch inventory", 2, 3)
 
240
            # we fetch only the referenced inventories because we do not
 
241
            # know for unselected inventories whether all their required
 
242
            # texts are present in the other repository - it could be
 
243
            # corrupt.
 
244
            to_weave.join(from_weave, pb=child_pb, msg='merge inventory',
 
245
                          version_ids=revs)
 
246
            from_weave.clear_cache()
 
247
        finally:
 
248
            child_pb.finished()
 
249
 
 
250
    def _generate_root_texts(self, revs):
 
251
        """This will be called by __fetch between fetching weave texts and
 
252
        fetching the inventory weave.
 
253
 
 
254
        Subclasses should override this if they need to generate root texts
 
255
        after fetching weave texts.
 
256
        """
 
257
        pass
219
258
 
220
259
 
221
260
class GenericRepoFetcher(RepoFetcher):
225
264
    It triggers a reconciliation after fetching to ensure integrity.
226
265
    """
227
266
 
228
 
    def _fetch_revision_texts(self, revs):
 
267
    def _fetch_revision_texts(self, revs, pb):
229
268
        """Fetch revision object texts"""
230
 
        rev_pb = bzrlib.ui.ui_factory.nested_progress_bar()
231
 
        try:
232
 
            to_txn = self.to_transaction = self.to_repository.get_transaction()
233
 
            count = 0
234
 
            total = len(revs)
235
 
            to_store = self.to_repository._revision_store
236
 
            for rev in revs:
237
 
                pb = bzrlib.ui.ui_factory.nested_progress_bar()
238
 
                try:
239
 
                    pb.update('copying revisions', count, total)
240
 
                    try:
241
 
                        sig_text = self.from_repository.get_signature_text(rev)
242
 
                        to_store.add_revision_signature_text(rev, sig_text, to_txn)
243
 
                    except errors.NoSuchRevision:
244
 
                        # not signed.
245
 
                        pass
246
 
                    to_store.add_revision(self.from_repository.get_revision(rev),
247
 
                                          to_txn)
248
 
                    count += 1
249
 
                finally:
250
 
                    pb.finished()
251
 
            # fixup inventory if needed: 
252
 
            # this is expensive because we have no inverse index to current ghosts.
253
 
            # but on local disk its a few seconds and sftp push is already insane.
254
 
            # so we just-do-it.
255
 
            # FIXME: repository should inform if this is needed.
256
 
            self.to_repository.reconcile()
257
 
        finally:
258
 
            rev_pb.finished()
 
269
        to_txn = self.to_transaction = self.to_repository.get_transaction()
 
270
        count = 0
 
271
        total = len(revs)
 
272
        to_store = self.to_repository._revision_store
 
273
        for rev in revs:
 
274
            pb.update('copying revisions', count, total)
 
275
            try:
 
276
                sig_text = self.from_repository.get_signature_text(rev)
 
277
                to_store.add_revision_signature_text(rev, sig_text, to_txn)
 
278
            except errors.NoSuchRevision:
 
279
                # not signed.
 
280
                pass
 
281
            to_store.add_revision(self.from_repository.get_revision(rev),
 
282
                                  to_txn)
 
283
            count += 1
 
284
        # fixup inventory if needed: 
 
285
        # this is expensive because we have no inverse index to current ghosts.
 
286
        # but on local disk its a few seconds and sftp push is already insane.
 
287
        # so we just-do-it.
 
288
        # FIXME: repository should inform if this is needed.
 
289
        self.to_repository.reconcile()
259
290
    
260
291
 
261
292
class KnitRepoFetcher(RepoFetcher):
266
297
    copy revision texts.
267
298
    """
268
299
 
269
 
    def _fetch_revision_texts(self, revs):
 
300
    def _fetch_revision_texts(self, revs, pb):
270
301
        # may need to be a InterRevisionStore call here.
271
302
        from_transaction = self.from_repository.get_transaction()
272
303
        to_transaction = self.to_repository.get_transaction()
307
338
 
308
339
        :param revs: A list of revision ids
309
340
        """
 
341
        # In case that revs is not a list.
 
342
        revs = list(revs)
310
343
        while revs:
311
344
            for tree in self.source.revision_trees(revs[:100]):
312
345
                if tree.inventory.revision_id is None:
325
358
        to_store = self.target.weave_store
326
359
        for tree in self.iter_rev_trees(revs):
327
360
            revision_id = tree.inventory.root.revision
328
 
            root_id = tree.inventory.root.file_id
 
361
            root_id = tree.get_root_id()
329
362
            parents = inventory_weave.get_parents(revision_id)
330
363
            if root_id not in versionedfile:
331
364
                versionedfile[root_id] = to_store.get_weave_or_empty(root_id, 
332
365
                    self.target.get_transaction())
333
 
            parent_texts[root_id] = versionedfile[root_id].add_lines(
 
366
            _, _, parent_texts[root_id] = versionedfile[root_id].add_lines(
334
367
                revision_id, parents, [], parent_texts)
335
368
 
336
369
    def regenerate_inventory(self, revs):
340
373
        stored in the target (reserializing it in a different format).
341
374
        :param revs: The revisions to include
342
375
        """
343
 
        inventory_weave = self.source.get_inventory_weave()
344
376
        for tree in self.iter_rev_trees(revs):
345
 
            parents = inventory_weave.get_parents(tree.get_revision_id())
 
377
            parents = tree.get_parent_ids()
346
378
            self.target.add_inventory(tree.get_revision_id(), tree.inventory,
347
379
                                      parents)
348
380
 
350
382
class Model1toKnit2Fetcher(GenericRepoFetcher):
351
383
    """Fetch from a Model1 repository into a Knit2 repository
352
384
    """
353
 
    def __init__(self, to_repository, from_repository, last_revision=None, 
354
 
                 pb=None):
 
385
    def __init__(self, to_repository, from_repository, last_revision=None,
 
386
                 pb=None, find_ghosts=True):
355
387
        self.helper = Inter1and2Helper(from_repository, to_repository)
356
388
        GenericRepoFetcher.__init__(self, to_repository, from_repository,
357
 
                                    last_revision, pb)
 
389
            last_revision, pb, find_ghosts)
358
390
 
359
 
    def _fetch_weave_texts(self, revs):
360
 
        GenericRepoFetcher._fetch_weave_texts(self, revs)
361
 
        # Now generate a weave for the tree root
 
391
    def _generate_root_texts(self, revs):
362
392
        self.helper.generate_root_texts(revs)
363
393
 
364
 
    def _fetch_inventory_weave(self, revs):
 
394
    def _fetch_inventory_weave(self, revs, pb):
365
395
        self.helper.regenerate_inventory(revs)
366
396
 
367
397
 
369
399
    """Fetch from a Knit1 repository into a Knit2 repository"""
370
400
 
371
401
    def __init__(self, to_repository, from_repository, last_revision=None, 
372
 
                 pb=None):
 
402
                 pb=None, find_ghosts=True):
373
403
        self.helper = Inter1and2Helper(from_repository, to_repository)
374
404
        KnitRepoFetcher.__init__(self, to_repository, from_repository,
375
 
                                 last_revision, pb)
 
405
            last_revision, pb, find_ghosts)
376
406
 
377
 
    def _fetch_weave_texts(self, revs):
378
 
        KnitRepoFetcher._fetch_weave_texts(self, revs)
379
 
        # Now generate a weave for the tree root
 
407
    def _generate_root_texts(self, revs):
380
408
        self.helper.generate_root_texts(revs)
381
409
 
382
 
    def _fetch_inventory_weave(self, revs):
 
410
    def _fetch_inventory_weave(self, revs, pb):
383
411
        self.helper.regenerate_inventory(revs)
384
 
        
385
 
 
386
 
class Fetcher(object):
387
 
    """Backwards compatibility glue for branch.fetch()."""
388
 
 
389
 
    @deprecated_method(zero_eight)
390
 
    def __init__(self, to_branch, from_branch, last_revision=None, pb=None):
391
 
        """Please see branch.fetch()."""
392
 
        to_branch.fetch(from_branch, last_revision, pb)
 
412
 
 
413
 
 
414
class RemoteToOtherFetcher(GenericRepoFetcher):
 
415
 
 
416
    def _fetch_everything_for_search(self, search, pp):
 
417
        data_stream = self.from_repository.get_data_stream_for_search(search)
 
418
        self.to_repository.insert_data_stream(data_stream)
 
419
 
 
420