/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

More work on roundtrip push support.

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
    )
22
23
from bzrlib.repository import (
26
27
    NULL_REVISION,
27
28
    )
28
29
 
29
 
from bzrlib.plugins.git.converter import (
30
 
    BazaarObjectStore,
31
 
    )
32
30
from bzrlib.plugins.git.errors import (
33
31
    NoPushSupport,
34
32
    )
35
 
from bzrlib.plugins.git.mapping import (
36
 
    inventory_to_tree_and_blobs,
37
 
    revision_to_commit,
 
33
from bzrlib.plugins.git.object_store import (
 
34
    BazaarObjectStore,
38
35
    )
39
36
from bzrlib.plugins.git.repository import (
40
37
    GitRepository,
 
38
    LocalGitRepository,
41
39
    GitRepositoryFormat,
42
40
    )
 
41
from bzrlib.plugins.git.remote import (
 
42
    RemoteGitRepository,
 
43
    )
43
44
 
44
45
 
45
46
class MissingObjectsIterator(object):
47
48
 
48
49
    """
49
50
 
50
 
    def __init__(self, source, mapping, pb=None):
 
51
    def __init__(self, store, source, pb=None):
51
52
        """Create a new missing objects iterator.
52
53
 
53
54
        """
54
55
        self.source = source
55
 
        self._object_store = BazaarObjectStore(self.source, mapping)
56
 
        self._revids = set()
57
 
        self._sent_shas = set()
 
56
        self._object_store = store
58
57
        self._pending = []
59
58
        self.pb = pb
60
59
 
61
 
    def import_revisions(self, revids):
62
 
        self._revids.update(revids)
 
60
    def import_revisions(self, revids, roundtrip):
 
61
        """Import a set of revisions into this git repository.
 
62
 
 
63
        :param revids: Revision ids of revisions to import
 
64
        :param roundtrip: Whether to roundtrip bzr metadata
 
65
        """
63
66
        for i, revid in enumerate(revids):
64
67
            if self.pb:
65
68
                self.pb.update("pushing revisions", i, len(revids))
66
 
            git_commit = self.import_revision(revid)
 
69
            git_commit = self.import_revision(revid, roundtrip)
67
70
            yield (revid, git_commit)
68
71
 
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
 
 
 
72
    def import_revision(self, revid, roundtrip):
 
73
        """Import a revision into this Git repository.
 
74
 
 
75
        :param revid: Revision id of the revision
 
76
        :param roundtrip: Whether to roundtrip bzr metadata
90
77
        """
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)
 
78
        tree = self._object_store.tree_cache.revision_tree(revid)
 
79
        rev = self.source.get_revision(revid)
 
80
        commit = None
 
81
        for path, obj, ie in self._object_store._revision_to_objects(rev, tree,
 
82
            roundtrip):
 
83
            if obj.type_name == "commit":
 
84
                commit = obj
 
85
            self._pending.append((obj, path))
107
86
        return commit.id
108
87
 
109
88
    def __len__(self):
110
89
        return len(self._pending)
111
90
 
112
91
    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)   
 
92
        return iter(self._pending)
119
93
 
120
94
 
121
95
class InterToGitRepository(InterRepository):
123
97
 
124
98
    _matching_repo_format = GitRepositoryFormat()
125
99
 
 
100
    def __init__(self, source, target):
 
101
        super(InterToGitRepository, self).__init__(source, target)
 
102
        self.mapping = self.target.get_mapping()
 
103
        self.source_store = BazaarObjectStore(self.source, self.mapping)
 
104
 
126
105
    @staticmethod
127
106
    def _get_repo_format_to_test():
128
107
        return None
131
110
        """See InterRepository.copy_content."""
132
111
        self.fetch(revision_id, pb, find_ghosts=False)
133
112
 
134
 
    def fetch(self, revision_id=None, pb=None, find_ghosts=False, 
135
 
            fetch_spec=None):
136
 
        raise NoPushSupport()
137
 
 
138
 
    def missing_revisions(self, stop_revision):
139
 
        if stop_revision is None:
140
 
            raise NotImplementedError
 
113
    def dfetch_refs(self, update_refs):
 
114
        """Fetch non-roundtripped revisions into the target repository.
 
115
 
 
116
        :param update_refs: Generate refs to fetch. Receives dictionary
 
117
            with old names to old git shas. Should return a dictionary
 
118
            of new names to Bazaar revision ids.
 
119
        :return: revision id map, old refs dictionary and new refs dictionary
 
120
        """
 
121
        raise NotImplementedError(self.dfetch_refs)
 
122
 
 
123
    def fetch_refs(self, update_refs):
 
124
        """Fetch possibly roundtripped revisions into the target repository.
 
125
 
 
126
        :param update_refs: Generate refs to fetch. Receives dictionary
 
127
            with old refs (git shas), returns dictionary of new names to
 
128
            git shas.
 
129
        :return: old refs, new refs
 
130
        """
 
131
        raise NotImplementedError(self.fetch_refs)
 
132
 
 
133
 
 
134
class InterToLocalGitRepository(InterToGitRepository):
 
135
    """InterBranch implementation between a Bazaar and a Git repository."""
 
136
 
 
137
    def __init__(self, source, target):
 
138
        super(InterToLocalGitRepository, self).__init__(source, target)
 
139
        self.target_store = self.target._git.object_store
 
140
        self.target_refs = self.target._git.refs
 
141
 
 
142
    def _revision_needs_fetching(self, sha_id, revid):
 
143
        if revid == NULL_REVISION:
 
144
            return False
 
145
        if sha_id is None:
 
146
            try:
 
147
                sha_id = self.source_store._lookup_revision_sha1(revid)
 
148
            except KeyError:
 
149
                return False
 
150
        try:
 
151
            return (sha_id not in self.target_store)
 
152
        except errors.NoSuchRevision:
 
153
            # Ghost, can't push
 
154
            return False
 
155
 
 
156
    def missing_revisions(self, stop_revisions):
 
157
        """Find the revisions that are missing from the target repository.
 
158
 
 
159
        :param stop_revisions: Revisions to check for (tuples with
 
160
            Git SHA1, bzr revid)
 
161
        :return: sequence of missing revisions, in topological order
 
162
        :raise: NoSuchRevision if the stop_revisions are not present in
 
163
            the source
 
164
        """
 
165
        revid_sha_map = {}
 
166
        stop_revids = []
 
167
        stop_sha1s = []
 
168
        for (sha1, revid) in stop_revisions:
 
169
            if sha1 is not None and revid is not None:
 
170
                revid_sha_map[revid] = sha1
 
171
            elif sha1 is not None:
 
172
                stop_sha1s.append(sha1)
 
173
            else:
 
174
                assert revid is not None
 
175
                stop_revids.append(revid)
141
176
        missing = []
 
177
        graph = self.source.get_graph()
142
178
        pb = ui.ui_factory.nested_progress_bar()
143
179
        try:
144
 
            graph = self.source.get_graph()
145
 
            for revid, _ in graph.iter_ancestry([stop_revision]):
 
180
            for revid, _ in graph.iter_ancestry(stop_revids):
 
181
                assert type(revid) is str
146
182
                pb.update("determining revisions to fetch", len(missing))
147
 
                if not self.target.has_revision(revid):
 
183
                sha1 = revid_sha_map.get(revid)
 
184
                if self._revision_needs_fetching(sha1, revid):
148
185
                    missing.append(revid)
149
 
            return graph.iter_topo_order(missing)
150
186
        finally:
151
187
            pb.finished()
152
 
 
153
 
    def dfetch(self, stop_revision=None):
 
188
        for sha1 in stop_sha1s:
 
189
            try:
 
190
                (kind, (revid, tree_sha, verifiers)) = self.source_store.lookup_git_sha(sha1)
 
191
            except KeyError:
 
192
                continue
 
193
            else:
 
194
                missing.append(revid)
 
195
                revid_sha_map[revid] = sha1
 
196
        return graph.iter_topo_order(missing)
 
197
 
 
198
    def _get_target_bzr_refs(self):
 
199
        """Return a dictionary with references.
 
200
 
 
201
        :return: Dictionary with reference names as keys and tuples
 
202
            with Git SHA, Bazaar revid as values.
 
203
        """
 
204
        bzr_refs = {}
 
205
        refs = self.target._git.get_refs()
 
206
        for k, v in refs.iteritems():
 
207
            try:
 
208
                (kind, type_data) = self.source_store.lookup_git_sha(v)
 
209
            except KeyError:
 
210
                revid = None
 
211
            else:
 
212
                if kind == "commit":
 
213
                    revid = type_data[0]
 
214
                else:
 
215
                    revid = None
 
216
            bzr_refs[k] = (v, revid)
 
217
        return bzr_refs
 
218
 
 
219
    def fetch_refs(self, update_refs):
 
220
        self.source.lock_read()
 
221
        try:
 
222
            old_refs = self._get_target_bzr_refs()
 
223
            new_refs = update_refs(old_refs)
 
224
            self.fetch(mapped_refs=new_refs.values())
 
225
        finally:
 
226
            self.source.unlock()
 
227
        return old_refs, new_refs
 
228
 
 
229
    def dfetch_refs(self, update_refs):
 
230
        self.source.lock_read()
 
231
        try:
 
232
            old_refs = self._get_target_bzr_refs()
 
233
            new_refs = update_refs(old_refs)
 
234
            revidmap, gitidmap = self.dfetch(new_refs.values())
 
235
            for name, (gitid, revid) in new_refs.iteritems():
 
236
                if gitid is None:
 
237
                    try:
 
238
                        gitid = gitidmap[revid]
 
239
                    except KeyError:
 
240
                        gitid = self.source_store._lookup_revision_sha1(revid)
 
241
                self.target._git.refs[name] = gitid
 
242
                new_refs[name] = (gitid, self.source_store.lookup_git_sha(gitid)[1][0])
 
243
        finally:
 
244
            self.source.unlock()
 
245
        return revidmap, old_refs, new_refs
 
246
 
 
247
    def _get_missing_objects_iterator(self, pb):
 
248
        return MissingObjectsIterator(self.source_store, self.source, pb)
 
249
 
 
250
    def dfetch(self, stop_revisions):
154
251
        """Import the gist of the ancestry of a particular revision."""
 
252
        gitidmap = {}
155
253
        revidmap = {}
156
 
        mapping = self.target.get_mapping()
157
254
        self.source.lock_read()
158
255
        try:
159
 
            todo = [revid for revid in self.missing_revisions(stop_revision) if revid != NULL_REVISION]
 
256
            todo = list(self.missing_revisions(stop_revisions))
160
257
            pb = ui.ui_factory.nested_progress_bar()
161
258
            try:
162
 
                object_generator = MissingObjectsIterator(self.source, mapping, pb)
 
259
                object_generator = self._get_missing_objects_iterator(pb)
163
260
                for old_bzr_revid, git_commit in object_generator.import_revisions(
164
 
                    todo):
165
 
                    new_bzr_revid = mapping.revision_id_foreign_to_bzr(git_commit)
 
261
                    todo, roundtrip=False):
 
262
                    new_bzr_revid = self.mapping.revision_id_foreign_to_bzr(git_commit)
166
263
                    revidmap[old_bzr_revid] = new_bzr_revid
167
 
                self.target._git.object_store.add_objects(object_generator) 
168
 
            finally:
169
 
                pb.finished()
170
 
        finally:
171
 
            self.source.unlock()
172
 
        return revidmap
173
 
 
174
 
    @staticmethod
175
 
    def is_compatible(source, target):
176
 
        """Be compatible with GitRepository."""
177
 
        return (not isinstance(source, GitRepository) and 
178
 
                isinstance(target, GitRepository))
 
264
                    gitidmap[old_bzr_revid] = git_commit
 
265
                self.target_store.add_objects(object_generator)
 
266
            finally:
 
267
                pb.finished()
 
268
        finally:
 
269
            self.source.unlock()
 
270
        return revidmap, gitidmap
 
271
 
 
272
    def fetch(self, revision_id=None, pb=None, find_ghosts=False,
 
273
            fetch_spec=None, mapped_refs=None):
 
274
        if mapped_refs is not None:
 
275
            stop_revisions = mapped_refs
 
276
        elif revision_id is not None:
 
277
            stop_revisions = [(None, revision_id)]
 
278
        elif fetch_spec is not None:
 
279
            stop_revisions = [(None, revid) for revid in fetch_spec.heads]
 
280
        else:
 
281
            stop_revisions = [(None, revid) for revid in self.source.all_revision_ids()]
 
282
        self.source.lock_read()
 
283
        try:
 
284
            todo = list(self.missing_revisions(stop_revisions))
 
285
            pb = ui.ui_factory.nested_progress_bar()
 
286
            try:
 
287
                object_generator = self._get_missing_objects_iterator(pb)
 
288
                for (revid, git_sha) in object_generator.import_revisions(
 
289
                    todo, roundtrip=True):
 
290
                    try:
 
291
                        self.mapping.revision_id_bzr_to_foreign(revid)
 
292
                    except errors.InvalidRevisionId:
 
293
                        self.target_refs[self.mapping.revid_as_refname(revid)] = git_sha
 
294
                self.target_store.add_objects(object_generator)
 
295
            finally:
 
296
                pb.finished()
 
297
        finally:
 
298
            self.source.unlock()
 
299
 
 
300
    @staticmethod
 
301
    def is_compatible(source, target):
 
302
        """Be compatible with GitRepository."""
 
303
        return (not isinstance(source, GitRepository) and
 
304
                isinstance(target, LocalGitRepository))
 
305
 
 
306
 
 
307
class InterToRemoteGitRepository(InterToGitRepository):
 
308
 
 
309
    def dfetch_refs(self, update_refs):
 
310
        """Import the gist of the ancestry of a particular revision."""
 
311
        revidmap = {}
 
312
        def determine_wants(old_refs):
 
313
            ret = {}
 
314
            self.old_refs = old_refs
 
315
            self.new_refs = update_refs(self.old_refs)
 
316
            for name, (gitid, revid) in self.new_refs.iteritems():
 
317
                if gitid is None:
 
318
                    ret[name] = self.source_store._lookup_revision_sha1(revid)
 
319
                else:
 
320
                    ret[name] = gitid
 
321
            return ret
 
322
        self.source.lock_read()
 
323
        try:
 
324
            new_refs = self.target.send_pack(determine_wants,
 
325
                    self.source_store.generate_lossy_pack_contents)
 
326
        finally:
 
327
            self.source.unlock()
 
328
        return revidmap, self.old_refs, self.new_refs
 
329
 
 
330
    def fetch_refs(self, update_refs):
 
331
        raise NoPushSupport()
 
332
 
 
333
    @staticmethod
 
334
    def is_compatible(source, target):
 
335
        """Be compatible with GitRepository."""
 
336
        return (not isinstance(source, GitRepository) and
 
337
                isinstance(target, RemoteGitRepository))