~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/fetch.py

  • Committer: Matt Nordhoff
  • Date: 2009-04-04 02:50:01 UTC
  • mfrom: (4253 +trunk)
  • mto: This revision was merged to the branch mainline in revision 4256.
  • Revision ID: mnordhoff@mattnordhoff.com-20090404025001-z1403k0tatmc8l91
Merge bzr.dev, fixing conflicts.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006, 2008 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
12
12
#
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
 
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
17
 
18
18
"""Copying of history from one branch to another.
21
21
that has merged into it.  As the first step of a merge, pull, or
22
22
branch operation we copy history from the source into the destination
23
23
branch.
24
 
 
25
 
The copying is done in a slightly complicated order.  We don't want to
26
 
add a revision to the store until everything it refers to is also
27
 
stored, so that if a revision is present we can totally recreate it.
28
 
However, we can't know what files are included in a revision until we
29
 
read its inventory.  So we query the inventory store of the source for
30
 
the ids we need, and then pull those ids and then return to the inventories.
31
24
"""
32
25
 
33
26
import operator
34
27
 
35
28
import bzrlib
36
 
import bzrlib.errors as errors
 
29
from bzrlib import (
 
30
    errors,
 
31
    symbol_versioning,
 
32
    )
37
33
from bzrlib.errors import InstallFailed
38
34
from bzrlib.progress import ProgressPhase
39
35
from bzrlib.revision import NULL_REVISION
40
36
from bzrlib.tsort import topo_sort
41
37
from bzrlib.trace import mutter
42
38
import bzrlib.ui
43
 
from bzrlib.versionedfile import filter_absent, FulltextContentFactory
44
 
 
45
 
# TODO: Avoid repeatedly opening weaves so many times.
46
 
 
47
 
# XXX: This doesn't handle ghost (not present in branch) revisions at
48
 
# all yet.  I'm not sure they really should be supported.
49
 
 
50
 
# NOTE: This doesn't copy revisions which may be present but not
51
 
# merged into the last revision.  I'm not sure we want to do that.
52
 
 
53
 
# - get a list of revisions that need to be pulled in
54
 
# - for each one, pull in that revision file
55
 
#   and get the inventory, and store the inventory with right
56
 
#   parents.
57
 
# - and get the ancestry, and store that with right parents too
58
 
# - and keep a note of all file ids and version seen
59
 
# - then go through all files; for each one get the weave,
60
 
#   and add in all file versions
 
39
from bzrlib.versionedfile import FulltextContentFactory
61
40
 
62
41
 
63
42
class RepoFetcher(object):
64
43
    """Pull revisions and texts from one repository to another.
65
44
 
66
 
    last_revision
67
 
        if set, try to limit to the data this revision references.
68
 
 
69
 
    after running:
70
 
    count_copied -- number of revisions copied
71
 
 
72
45
    This should not be used directly, it's essential a object to encapsulate
73
46
    the logic in InterRepository.fetch().
74
47
    """
75
48
 
76
 
    def __init__(self, to_repository, from_repository, last_revision=None, pb=None,
77
 
        find_ghosts=True):
 
49
    def __init__(self, to_repository, from_repository, last_revision=None,
 
50
        pb=None, find_ghosts=True, fetch_spec=None):
78
51
        """Create a repo fetcher.
79
52
 
 
53
        :param last_revision: If set, try to limit to the data this revision
 
54
            references.
80
55
        :param find_ghosts: If True search the entire history for ghosts.
81
56
        :param _write_group_acquired_callable: Don't use; this parameter only
82
57
            exists to facilitate a hack done in InterPackRepo.fetch.  We would
83
58
            like to remove this parameter.
 
59
        :param pb: ProgressBar object to use; deprecated and ignored.
 
60
            This method will just create one on top of the stack.
84
61
        """
85
 
        # result variables.
86
 
        self.failed_revisions = []
87
 
        self.count_copied = 0
 
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
88
67
        if to_repository.has_same_location(from_repository):
89
68
            # repository.fetch should be taking care of this case.
90
69
            raise errors.BzrError('RepoFetcher run '
95
74
        self.sink = to_repository._get_sink()
96
75
        # must not mutate self._last_revision as its potentially a shared instance
97
76
        self._last_revision = last_revision
 
77
        self._fetch_spec = fetch_spec
98
78
        self.find_ghosts = find_ghosts
99
 
        if pb is None:
100
 
            self.pb = bzrlib.ui.ui_factory.nested_progress_bar()
101
 
            self.nested_pb = self.pb
102
 
        else:
103
 
            self.pb = pb
104
 
            self.nested_pb = None
105
79
        self.from_repository.lock_read()
 
80
        mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
 
81
               self.from_repository, self.from_repository._format,
 
82
               self.to_repository, self.to_repository._format)
106
83
        try:
107
 
            self.to_repository.lock_write()
108
 
            try:
109
 
                self.to_repository.start_write_group()
110
 
                try:
111
 
                    self.__fetch()
112
 
                except:
113
 
                    self.to_repository.abort_write_group(suppress_errors=True)
114
 
                    raise
115
 
                else:
116
 
                    self.to_repository.commit_write_group()
117
 
            finally:
118
 
                try:
119
 
                    if self.nested_pb is not None:
120
 
                        self.nested_pb.finished()
121
 
                finally:
122
 
                    self.to_repository.unlock()
 
84
            self.__fetch()
123
85
        finally:
124
86
            self.from_repository.unlock()
125
87
 
126
88
    def __fetch(self):
127
89
        """Primary worker function.
128
90
 
129
 
        This initialises all the needed variables, and then fetches the 
 
91
        This initialises all the needed variables, and then fetches the
130
92
        requested revisions, finally clearing the progress bar.
131
93
        """
132
94
        # Roughly this is what we're aiming for fetch to become:
137
99
        # assert not missing
138
100
        self.count_total = 0
139
101
        self.file_ids_names = {}
140
 
        pp = ProgressPhase('Transferring', 4, self.pb)
 
102
        pb = bzrlib.ui.ui_factory.nested_progress_bar()
 
103
        pb.show_pct = pb.show_count = False
141
104
        try:
142
 
            pp.next_phase()
 
105
            pb.update("Finding revisions", 0, 2)
143
106
            search = self._revids_to_fetch()
144
107
            if search is None:
145
108
                return
146
 
            self._fetch_everything_for_search(search, pp)
 
109
            pb.update("Fetching revisions", 1, 2)
 
110
            self._fetch_everything_for_search(search)
147
111
        finally:
148
 
            self.pb.clear()
 
112
            pb.finished()
149
113
 
150
 
    def _fetch_everything_for_search(self, search, pp):
 
114
    def _fetch_everything_for_search(self, search):
151
115
        """Fetch all data for the given set of revisions."""
152
116
        # The first phase is "file".  We pass the progress bar for it directly
153
117
        # into item_keys_introduced_by, which has more information about how
157
121
        # item_keys_introduced_by should have a richer API than it does at the
158
122
        # moment, so that it can feed the progress information back to this
159
123
        # function?
160
 
        self.pb = bzrlib.ui.ui_factory.nested_progress_bar()
 
124
        if (self.from_repository._format.rich_root_data and
 
125
            not self.to_repository._format.rich_root_data):
 
126
            raise errors.IncompatibleRepositories(
 
127
                self.from_repository, self.to_repository,
 
128
                "different rich-root support")
 
129
        pb = bzrlib.ui.ui_factory.nested_progress_bar()
161
130
        try:
 
131
            pb.update("Get stream source")
 
132
            source = self.from_repository._get_source(
 
133
                self.to_repository._format)
 
134
            stream = source.get_stream(search)
162
135
            from_format = self.from_repository._format
163
 
            stream = self.get_stream(search, pp)
164
 
            self.sink.insert_stream(stream, from_format)
 
136
            pb.update("Inserting stream")
 
137
            resume_tokens, missing_keys = self.sink.insert_stream(
 
138
                stream, from_format, [])
 
139
            if missing_keys:
 
140
                pb.update("Missing keys")
 
141
                stream = source.get_stream_for_missing_keys(missing_keys)
 
142
                pb.update("Inserting missing keys")
 
143
                resume_tokens, missing_keys = self.sink.insert_stream(
 
144
                    stream, from_format, resume_tokens)
 
145
            if missing_keys:
 
146
                raise AssertionError(
 
147
                    "second push failed to complete a fetch %r." % (
 
148
                        missing_keys,))
 
149
            if resume_tokens:
 
150
                raise AssertionError(
 
151
                    "second push failed to commit the fetch %r." % (
 
152
                        resume_tokens,))
 
153
            pb.update("Finishing stream")
165
154
            self.sink.finished()
166
155
        finally:
167
 
            if self.pb is not None:
168
 
                self.pb.finished()
169
 
        
170
 
    def get_stream(self, search, pp):
171
 
        phase = 'file'
172
 
        revs = search.get_keys()
173
 
        graph = self.from_repository.get_graph()
174
 
        revs = list(graph.iter_topo_order(revs))
175
 
        data_to_fetch = self.from_repository.item_keys_introduced_by(
176
 
            revs, self.pb)
177
 
        text_keys = []
178
 
        for knit_kind, file_id, revisions in data_to_fetch:
179
 
            if knit_kind != phase:
180
 
                phase = knit_kind
181
 
                # Make a new progress bar for this phase
182
 
                self.pb.finished()
183
 
                pp.next_phase()
184
 
                self.pb = bzrlib.ui.ui_factory.nested_progress_bar()
185
 
            if knit_kind == "file":
186
 
                # Accumulate file texts
187
 
                text_keys.extend([(file_id, revision) for revision in
188
 
                    revisions])
189
 
            elif knit_kind == "inventory":
190
 
                # Now copy the file texts.
191
 
                to_texts = self.to_repository.texts
192
 
                from_texts = self.from_repository.texts
193
 
                yield ('texts', from_texts.get_record_stream(
194
 
                    text_keys, self.to_repository._fetch_order,
195
 
                    not self.to_repository._fetch_uses_deltas))
196
 
                # Cause an error if a text occurs after we have done the
197
 
                # copy.
198
 
                text_keys = None
199
 
                # Before we process the inventory we generate the root
200
 
                # texts (if necessary) so that the inventories references
201
 
                # will be valid.
202
 
                for _ in self._generate_root_texts(revs):
203
 
                    yield _
204
 
                # NB: This currently reopens the inventory weave in source;
205
 
                # using a single stream interface instead would avoid this.
206
 
                self.pb.update("fetch inventory", 0, 1)
207
 
                from_weave = self.from_repository.inventories
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
 
                yield ('inventories', from_weave.get_record_stream(
213
 
                    [(rev_id,) for rev_id in revs],
214
 
                    self.inventory_fetch_order(),
215
 
                    not self.delta_on_metadata()))
216
 
            elif knit_kind == "signatures":
217
 
                # Nothing to do here; this will be taken care of when
218
 
                # _fetch_revision_texts happens.
219
 
                pass
220
 
            elif knit_kind == "revisions":
221
 
                for _ in self._fetch_revision_texts(revs, self.pb):
222
 
                    yield _
223
 
            else:
224
 
                raise AssertionError("Unknown knit kind %r" % knit_kind)
225
 
        self.count_copied += len(revs)
 
156
            pb.finished()
226
157
 
227
158
    def _revids_to_fetch(self):
228
159
        """Determines the exact revisions needed from self.from_repository to
230
161
 
231
162
        If no revisions need to be fetched, then this just returns None.
232
163
        """
 
164
        if self._fetch_spec is not None:
 
165
            return self._fetch_spec
233
166
        mutter('fetch up to rev {%s}', self._last_revision)
234
167
        if self._last_revision is NULL_REVISION:
235
168
            # explicit limit of no revisions needed
244
177
        except errors.NoSuchRevision, e:
245
178
            raise InstallFailed([self._last_revision])
246
179
 
247
 
    def _fetch_revision_texts(self, revs, pb):
248
 
        # fetch signatures first and then the revision texts
249
 
        # may need to be a InterRevisionStore call here.
250
 
        from_sf = self.from_repository.signatures
251
 
        # A missing signature is just skipped.
252
 
        keys = [(rev_id,) for rev_id in revs]
253
 
        signatures = filter_absent(from_sf.get_record_stream(
254
 
            keys,
255
 
            self.to_repository._fetch_order,
256
 
            not self.to_repository._fetch_uses_deltas))
257
 
        # If a revision has a delta, this is actually expanded inside the
258
 
        # insert_record_stream code now, which is an alternate fix for
259
 
        # bug #261339
260
 
        from_rf = self.from_repository.revisions
261
 
        revisions = from_rf.get_record_stream(
262
 
            keys,
263
 
            self.to_repository._fetch_order,
264
 
            not self.delta_on_metadata())
265
 
        return [('signatures', signatures), ('revisions', revisions)]
266
 
 
267
 
    def _generate_root_texts(self, revs):
268
 
        """This will be called by __fetch between fetching weave texts and
269
 
        fetching the inventory weave.
270
 
 
271
 
        Subclasses should override this if they need to generate root texts
272
 
        after fetching weave texts.
273
 
        """
274
 
        return []
275
 
 
276
 
    def inventory_fetch_order(self):
277
 
        return self.to_repository._fetch_order
278
 
 
279
 
    def delta_on_metadata(self):
280
 
        src_serializer = self.from_repository._format._serializer
281
 
        target_serializer = self.to_repository._format._serializer
282
 
        return (self.to_repository._fetch_uses_deltas and
283
 
            src_serializer == target_serializer)
284
 
 
285
180
 
286
181
class Inter1and2Helper(object):
287
182
    """Helper for operations that convert data from model 1 and 2
288
 
    
 
183
 
289
184
    This is for use by fetchers and converters.
290
185
    """
291
186
 
370
265
                        rev_id_to_root_id.get(parent, root_id) == root_id)
371
266
                yield FulltextContentFactory(key, parent_keys, None, '')
372
267
        return [('texts', yield_roots())]
373
 
 
374
 
 
375
 
class Model1toKnit2Fetcher(RepoFetcher):
376
 
    """Fetch from a Model1 repository into a Knit2 repository
377
 
    """
378
 
    def __init__(self, to_repository, from_repository, last_revision=None,
379
 
                 pb=None, find_ghosts=True):
380
 
        self.helper = Inter1and2Helper(from_repository)
381
 
        RepoFetcher.__init__(self, to_repository, from_repository,
382
 
            last_revision, pb, find_ghosts)
383
 
 
384
 
    def _generate_root_texts(self, revs):
385
 
        return self.helper.generate_root_texts(revs)
386
 
 
387
 
    def inventory_fetch_order(self):
388
 
        return 'topological'
389
 
 
390
 
Knit1to2Fetcher = Model1toKnit2Fetcher