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

Fix regression in git-import.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2009 Jelmer Vernooij <jelmer@samba.org>
 
1
# Copyright (C) 2009-2010 Jelmer Vernooij <jelmer@samba.org>
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
17
17
"""Push implementation that simply prints message saying push is not supported."""
18
18
 
19
19
from bzrlib import (
 
20
    errors,
20
21
    ui,
21
22
    )
 
23
from bzrlib.graph import (
 
24
    PendingAncestryResult,
 
25
    )
22
26
from bzrlib.repository import (
23
27
    InterRepository,
24
28
    )
47
51
 
48
52
    """
49
53
 
50
 
    def __init__(self, source, mapping, pb=None):
 
54
    def __init__(self, store, source, pb=None):
51
55
        """Create a new missing objects iterator.
52
56
 
53
57
        """
54
58
        self.source = source
55
 
        self._object_store = BazaarObjectStore(self.source, mapping)
56
 
        self._revids = set()
57
 
        self._sent_shas = set()
 
59
        self._object_store = store
58
60
        self._pending = []
59
61
        self.pb = pb
60
62
 
61
 
    def import_revisions(self, revids):
62
 
        self._revids.update(revids)
 
63
    def import_revisions(self, revids, roundtrip):
 
64
        """Import a set of revisions into this git repository.
 
65
 
 
66
        :param revids: Revision ids of revisions to import
 
67
        :param roundtrip: Whether to roundtrip bzr metadata
 
68
        """
63
69
        for i, revid in enumerate(revids):
64
70
            if self.pb:
65
71
                self.pb.update("pushing revisions", i, len(revids))
66
 
            git_commit = self.import_revision(revid)
 
72
            git_commit = self.import_revision(revid, roundtrip)
67
73
            yield (revid, git_commit)
68
74
 
69
 
    def need_sha(self, sha):
70
 
        if sha in self._sent_shas:
71
 
            return False
72
 
        (type, (fileid, revid)) = self._object_store._idmap.lookup_git_sha(sha)
73
 
        assert type in ("blob", "tree")
74
 
        if revid in self._revids:
75
 
            # Not sent yet, and part of the set of revisions to send
76
 
            return True
77
 
        # Not changed in the revisions to send, so either not necessary
78
 
        # or already present remotely (as git doesn't do ghosts)
79
 
        return False
80
 
 
81
 
    def queue(self, sha, obj, path, ie=None, inv=None):
82
 
        if obj is None:
83
 
            obj = (ie, inv)
84
 
        self._pending.append((obj, path))
85
 
        self._sent_shas.add(sha)
86
 
 
87
 
    def import_revision(self, revid):
88
 
        """Import the gist of a revision into this Git repository.
89
 
 
 
75
    def import_revision(self, revid, roundtrip):
 
76
        """Import a revision into this Git repository.
 
77
 
 
78
        :param revid: Revision id of the revision
 
79
        :param roundtrip: Whether to roundtrip bzr metadata
90
80
        """
91
 
        inv = self.source.get_inventory(revid)
92
 
        todo = [inv.root]
93
 
        tree_sha = None
94
 
        while todo:
95
 
            ie = todo.pop()
96
 
            (sha, object) = self._object_store._get_ie_object_or_sha1(ie, inv)
97
 
            if ie.parent_id is None:
98
 
                tree_sha = sha
99
 
            if not self.need_sha(sha):
100
 
                continue
101
 
            self.queue(sha, object, inv.id2path(ie.file_id), ie, inv)
102
 
            if ie.kind == "directory":
103
 
                todo.extend(ie.children.values())
104
 
        assert tree_sha is not None
105
 
        commit = self._object_store._get_commit(revid, tree_sha)
106
 
        self.queue(commit.id, commit, None)
 
81
        tree = self._object_store.tree_cache.revision_tree(revid)
 
82
        rev = self.source.get_revision(revid)
 
83
        commit = None
 
84
        for path, obj, ie in self._object_store._revision_to_objects(rev, tree,
 
85
            roundtrip):
 
86
            if obj.type_name == "commit":
 
87
                commit = obj
 
88
            self._pending.append((obj, path))
107
89
        return commit.id
108
90
 
109
91
    def __len__(self):
110
92
        return len(self._pending)
111
93
 
112
94
    def __iter__(self):
113
 
        for i, (object, path) in enumerate(self._pending):
114
 
            if self.pb:
115
 
                self.pb.update("writing pack objects", i, len(self))
116
 
            if isinstance(object, tuple):
117
 
                object = self._object_store._get_ie_object(*object)
118
 
            yield (object, path)   
 
95
        return iter(self._pending)
119
96
 
120
97
 
121
98
class InterToGitRepository(InterRepository):
126
103
    def __init__(self, source, target):
127
104
        super(InterToGitRepository, self).__init__(source, target)
128
105
        self.mapping = self.target.get_mapping()
 
106
        self.source_store = BazaarObjectStore(self.source, self.mapping)
129
107
 
130
108
    @staticmethod
131
109
    def _get_repo_format_to_test():
135
113
        """See InterRepository.copy_content."""
136
114
        self.fetch(revision_id, pb, find_ghosts=False)
137
115
 
138
 
    def fetch(self, revision_id=None, pb=None, find_ghosts=False, 
139
 
            fetch_spec=None):
140
 
        raise NoPushSupport()
 
116
    def dfetch_refs(self, update_refs):
 
117
        """Fetch non-roundtripped revisions into the target repository.
 
118
 
 
119
        :param update_refs: Generate refs to fetch. Receives dictionary 
 
120
            with old names to old git shas. Should return a dictionary
 
121
            of new names to Bazaar revision ids.
 
122
        :return: revision id map, old refs dictionary and new refs dictionary
 
123
        """
 
124
        raise NotImplementedError(self.dfetch_refs)
 
125
 
 
126
    def fetch_refs(self, update_refs):
 
127
        """Fetch possibly roundtripped revisions into the target repository.
 
128
 
 
129
        :param update_refs: Generate refs to fetch. Receives dictionary 
 
130
            with old refs (git shas), returns dictionary of new names to 
 
131
            git shas.
 
132
        :return: old refs, new refs
 
133
        """
 
134
        raise NotImplementedError(self.fetch_refs)
141
135
 
142
136
 
143
137
class InterToLocalGitRepository(InterToGitRepository):
144
 
 
145
 
    def missing_revisions(self, stop_revision):
146
 
        if stop_revision is None:
147
 
            raise NotImplementedError
 
138
    """InterBranch implementation between a Bazaar and a Git repository."""
 
139
 
 
140
    def __init__(self, source, target):
 
141
        super(InterToLocalGitRepository, self).__init__(source, target)
 
142
        self.target_store = self.target._git.object_store
 
143
        self.target_refs = self.target._git.refs
 
144
 
 
145
    def _revision_needs_fetching(self, revid):
 
146
        if revid == NULL_REVISION:
 
147
            return False
 
148
        try:
 
149
            sha_id = self.source_store._lookup_revision_sha1(revid)
 
150
        except KeyError:
 
151
            raise errors.NoSuchRevision(self.source, revid)
 
152
        try:
 
153
            return (sha_id not in self.target_store)
 
154
        except errors.NoSuchRevision:
 
155
            # Ghost, can't push
 
156
            return False
 
157
 
 
158
    def missing_revisions(self, stop_revisions):
 
159
        """Find the revisions that are missing from the target repository.
 
160
 
 
161
        :param stop_revisions: Revisions to check for
 
162
        :return: sequence of missing revisions, in topological order
 
163
        :raise: NoSuchRevision if the stop_revisions are not present in
 
164
            the source
 
165
        """
148
166
        missing = []
 
167
        graph = self.source.get_graph()
149
168
        pb = ui.ui_factory.nested_progress_bar()
150
169
        try:
151
 
            graph = self.source.get_graph()
152
 
            for revid, _ in graph.iter_ancestry([stop_revision]):
 
170
            for revid, _ in graph.iter_ancestry(stop_revisions):
 
171
                assert type(revid) is str
153
172
                pb.update("determining revisions to fetch", len(missing))
154
 
                if not self.target.has_revision(revid):
 
173
                if self._revision_needs_fetching(revid):
155
174
                    missing.append(revid)
156
 
            return graph.iter_topo_order(missing)
157
175
        finally:
158
176
            pb.finished()
159
 
 
160
 
    def dfetch_refs(self, refs):
161
 
        revidmap = {}
162
 
        new_refs = {}
163
 
        for name, revid in refs.iteritems():
164
 
            newrevidmap, newgitidmap = self.dfetch(revid)
165
 
            revidmap.update(newrevidmap)
166
 
            if revid in newgitidmap:
167
 
                gitid = newgitidmap[revid]
168
 
            else:
169
 
                gitid, _ = self.mapping.revision_id_bzr_to_foreign(revid)
170
 
            self.target._git.refs[name] = gitid
171
 
            new_refs[name] = gitid
172
 
        return revidmap, new_refs
173
 
 
174
 
    def dfetch(self, stop_revision=None):
 
177
        return graph.iter_topo_order(missing)
 
178
 
 
179
    def _get_target_bzr_refs(self):
 
180
        """Return a dictionary with references.
 
181
 
 
182
        :return: Dictionary with reference names as keys and tuples
 
183
            with Git SHA, Bazaar revid as values.
 
184
        """
 
185
        bzr_refs = {}
 
186
        refs = self.target._git.get_refs()
 
187
        for k, v in refs.iteritems():
 
188
            try:
 
189
                (kind, (revid, treesha)) = self.source_store.lookup_git_sha(v)
 
190
            except KeyError:
 
191
                revid = None
 
192
            bzr_refs[k] = (v, revid)
 
193
        return bzr_refs
 
194
 
 
195
    def fetch_refs(self, update_refs):
 
196
        self.source.lock_read()
 
197
        try:
 
198
            old_refs = self._get_target_bzr_refs()
 
199
            new_refs = update_refs(old_refs)
 
200
            # FIXME: Keep track of already looked up revid<->sha mappings
 
201
            fetch_spec = PendingAncestryResult(
 
202
                [revid for sha, revid in new_refs.values()], self.source)
 
203
            self.fetch(fetch_spec=fetch_spec)
 
204
        finally:
 
205
            self.source.unlock()
 
206
        return old_refs, new_refs
 
207
 
 
208
    def dfetch_refs(self, update_refs):
 
209
        self.source.lock_read()
 
210
        try:
 
211
            old_refs = self._get_target_bzr_refs()
 
212
            new_refs = update_refs(old_refs)
 
213
            revidmap, gitidmap = self.dfetch(new_refs.values())
 
214
            for name, (gitid, revid) in new_refs.iteritems():
 
215
                if gitid is None:
 
216
                    try:
 
217
                        gitid = gitidmap[revid]
 
218
                    except KeyError:
 
219
                        gitid = self.source_store._lookup_revision_sha1(revid)
 
220
                self.target._git.refs[name] = gitid
 
221
                new_refs[name] = (gitid, self.source_store.lookup_git_sha(gitid)[1][0])
 
222
        finally:
 
223
            self.source.unlock()
 
224
        return revidmap, old_refs, new_refs
 
225
 
 
226
    def _get_missing_objects_iterator(self, pb):
 
227
        return MissingObjectsIterator(self.source_store, self.source, pb)
 
228
 
 
229
    def dfetch(self, stop_revisions):
175
230
        """Import the gist of the ancestry of a particular revision."""
176
231
        gitidmap = {}
177
232
        revidmap = {}
178
233
        self.source.lock_read()
179
234
        try:
180
 
            todo = [revid for revid in self.missing_revisions(stop_revision) if revid != NULL_REVISION]
 
235
            todo = list(self.missing_revisions([revid for sha, revid in stop_revisions]))
181
236
            pb = ui.ui_factory.nested_progress_bar()
182
237
            try:
183
 
                object_generator = MissingObjectsIterator(self.source, self.mapping, pb)
 
238
                object_generator = self._get_missing_objects_iterator(pb)
184
239
                for old_bzr_revid, git_commit in object_generator.import_revisions(
185
 
                    todo):
 
240
                    todo, roundtrip=False):
186
241
                    new_bzr_revid = self.mapping.revision_id_foreign_to_bzr(git_commit)
187
242
                    revidmap[old_bzr_revid] = new_bzr_revid
188
243
                    gitidmap[old_bzr_revid] = git_commit
189
 
                self.target._git.object_store.add_objects(object_generator) 
 
244
                self.target_store.add_objects(object_generator)
190
245
            finally:
191
246
                pb.finished()
192
247
        finally:
193
248
            self.source.unlock()
194
249
        return revidmap, gitidmap
195
250
 
 
251
    def fetch(self, revision_id=None, pb=None, find_ghosts=False,
 
252
            fetch_spec=None):
 
253
        if revision_id is not None:
 
254
            stop_revisions = [revision_id]
 
255
        elif fetch_spec is not None:
 
256
            stop_revisions = fetch_spec.heads
 
257
        else:
 
258
            stop_revisions = self.source.all_revision_ids()
 
259
        self.source.lock_read()
 
260
        try:
 
261
            todo = list(self.missing_revisions(stop_revisions))
 
262
            pb = ui.ui_factory.nested_progress_bar()
 
263
            try:
 
264
                object_generator = self._get_missing_objects_iterator(pb)
 
265
                for (revid, git_sha) in object_generator.import_revisions(
 
266
                    todo, roundtrip=True):
 
267
                    try:
 
268
                        self.mapping.revision_id_bzr_to_foreign(revid)
 
269
                    except errors.InvalidRevisionId:
 
270
                        self.target_refs[self.mapping.revid_as_refname(revid)] = git_sha
 
271
                self.target_store.add_objects(object_generator)
 
272
            finally:
 
273
                pb.finished()
 
274
        finally:
 
275
            self.source.unlock()
 
276
 
196
277
    @staticmethod
197
278
    def is_compatible(source, target):
198
279
        """Be compatible with GitRepository."""
199
 
        return (not isinstance(source, GitRepository) and 
 
280
        return (not isinstance(source, GitRepository) and
200
281
                isinstance(target, LocalGitRepository))
201
282
 
202
283
 
203
284
class InterToRemoteGitRepository(InterToGitRepository):
204
285
 
205
 
    def dfetch_refs(self, new_refs):
 
286
    def dfetch_refs(self, update_refs):
206
287
        """Import the gist of the ancestry of a particular revision."""
207
288
        revidmap = {}
208
 
        def determine_wants(refs):
 
289
        def determine_wants(old_refs):
209
290
            ret = {}
210
 
            for name, revid in new_refs.iteritems():
211
 
                ret[name] = store._lookup_revision_sha1(revid)
 
291
            self.old_refs = old_refs
 
292
            self.new_refs = update_refs(self.old_refs)
 
293
            for name, (gitid, revid) in self.new_refs.iteritems():
 
294
                if gitid is None:
 
295
                    ret[name] = self.source_store._lookup_revision_sha1(revid)
 
296
                else:
 
297
                    ret[name] = gitid
212
298
            return ret
213
299
        self.source.lock_read()
214
300
        try:
215
 
            store = BazaarObjectStore(self.source, self.mapping)
216
 
            def generate_blob_contents(have, want):
217
 
                return store.iter_shas(store.find_missing_objects(have, want))
218
301
            new_refs = self.target.send_pack(determine_wants,
219
 
                    generate_blob_contents)
 
302
                    self.source_store.generate_lossy_pack_contents)
220
303
        finally:
221
304
            self.source.unlock()
222
 
        return revidmap, new_refs
 
305
        return revidmap, self.old_refs, self.new_refs
 
306
 
 
307
    def fetch_refs(self, update_refs):
 
308
        raise NoPushSupport()
223
309
 
224
310
    @staticmethod
225
311
    def is_compatible(source, target):
226
312
        """Be compatible with GitRepository."""
227
 
        return (not isinstance(source, GitRepository) and 
 
313
        return (not isinstance(source, GitRepository) and
228
314
                isinstance(target, RemoteGitRepository))