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

Add FOSDEM roundtripping notes.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2008 Canonical Ltd
 
2
#
 
3
# This program is free software; you can redistribute it and/or modify
 
4
# it under the terms of the GNU General Public License as published by
 
5
# the Free Software Foundation; either version 2 of the License, or
 
6
# (at your option) any later version.
 
7
#
 
8
# This program is distributed in the hope that it will be useful,
 
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
11
# GNU General Public License for more details.
 
12
#
 
13
# You should have received a copy of the GNU General Public License
 
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
 
16
 
 
17
from bzrlib import osutils, ui, urlutils
 
18
from bzrlib.errors import InvalidRevisionId
 
19
from bzrlib.inventory import Inventory
 
20
from bzrlib.repository import InterRepository
 
21
from bzrlib.trace import info
 
22
from bzrlib.tsort import topo_sort
 
23
 
 
24
from bzrlib.plugins.git.repository import (
 
25
        LocalGitRepository, 
 
26
        GitRepository, 
 
27
        GitFormat,
 
28
        )
 
29
from bzrlib.plugins.git.remote import RemoteGitRepository
 
30
 
 
31
import dulwich as git
 
32
from dulwich.client import SimpleFetchGraphWalker
 
33
from dulwich.objects import Commit
 
34
 
 
35
from cStringIO import StringIO
 
36
 
 
37
 
 
38
class BzrFetchGraphWalker(object):
 
39
    """GraphWalker implementation that uses a Bazaar repository."""
 
40
 
 
41
    def __init__(self, repository, mapping):
 
42
        self.repository = repository
 
43
        self.mapping = mapping
 
44
        self.done = set()
 
45
        self.heads = set(repository.all_revision_ids())
 
46
        self.parents = {}
 
47
 
 
48
    def __iter__(self):
 
49
        return iter(self.next, None)
 
50
 
 
51
    def ack(self, sha):
 
52
        revid = self.mapping.revision_id_foreign_to_bzr(sha)
 
53
        self.remove(revid)
 
54
 
 
55
    def remove(self, revid):
 
56
        self.done.add(revid)
 
57
        if revid in self.heads:
 
58
            self.heads.remove(revid)
 
59
        if revid in self.parents:
 
60
            for p in self.parents[revid]:
 
61
                self.remove(p)
 
62
 
 
63
    def next(self):
 
64
        while self.heads:
 
65
            ret = self.heads.pop()
 
66
            ps = self.repository.get_parent_map([ret])[ret]
 
67
            self.parents[ret] = ps
 
68
            self.heads.update([p for p in ps if not p in self.done])
 
69
            try:
 
70
                self.done.add(ret)
 
71
                return self.mapping.revision_id_bzr_to_foreign(ret)[0]
 
72
            except InvalidRevisionId:
 
73
                pass
 
74
        return None
 
75
 
 
76
 
 
77
def import_git_blob(repo, mapping, path, blob, inv, parent_invs, executable):
 
78
    """Import a git blob object into a bzr repository.
 
79
 
 
80
    :param repo: bzr repository
 
81
    :param path: Path in the tree
 
82
    :param blob: A git blob
 
83
    """
 
84
    file_id = mapping.generate_file_id(path)
 
85
    text_revision = inv.revision_id
 
86
    repo.texts.add_lines((file_id, text_revision),
 
87
        [(file_id, p[file_id].revision) for p in parent_invs if file_id in p],
 
88
        osutils.split_lines(blob.data))
 
89
    ie = inv.add_path(path, "file", file_id)
 
90
    ie.revision = text_revision
 
91
    ie.text_size = len(blob.data)
 
92
    ie.text_sha1 = osutils.sha_string(blob.data)
 
93
    ie.executable = executable
 
94
 
 
95
 
 
96
def import_git_tree(repo, mapping, path, tree, inv, parent_invs, lookup_object):
 
97
    """Import a git tree object into a bzr repository.
 
98
 
 
99
    :param repo: A Bzr repository object
 
100
    :param path: Path in the tree
 
101
    :param tree: A git tree object
 
102
    :param inv: Inventory object
 
103
    """
 
104
    file_id = mapping.generate_file_id(path)
 
105
    text_revision = inv.revision_id
 
106
    repo.texts.add_lines((file_id, text_revision),
 
107
        [(file_id, p[file_id].revision) for p in parent_invs if file_id in p],
 
108
        [])
 
109
    ie = inv.add_path(path, "directory", file_id)
 
110
    ie.revision = text_revision
 
111
    for mode, name, hexsha in tree.entries():
 
112
        entry_kind = (mode & 0700000) / 0100000
 
113
        basename = name.decode("utf-8")
 
114
        if path == "":
 
115
            child_path = name
 
116
        else:
 
117
            child_path = urlutils.join(path, name)
 
118
        if entry_kind == 0:
 
119
            tree = lookup_object(hexsha)
 
120
            import_git_tree(repo, mapping, child_path, tree, inv, parent_invs, lookup_object)
 
121
        elif entry_kind == 1:
 
122
            blob = lookup_object(hexsha)
 
123
            fs_mode = mode & 0777
 
124
            import_git_blob(repo, mapping, child_path, blob, inv, parent_invs, bool(fs_mode & 0111))
 
125
        else:
 
126
            raise AssertionError("Unknown blob kind, perms=%r." % (mode,))
 
127
 
 
128
 
 
129
def import_git_objects(repo, mapping, object_iter, pb=None):
 
130
    """Import a set of git objects into a bzr repository.
 
131
 
 
132
    :param repo: Bazaar repository
 
133
    :param mapping: Mapping to use
 
134
    :param object_iter: Iterator over Git objects.
 
135
    """
 
136
    # TODO: a more (memory-)efficient implementation of this
 
137
    graph = []
 
138
    root_trees = {}
 
139
    revisions = {}
 
140
    # Find and convert commit objects
 
141
    for o in object_iter.iterobjects():
 
142
        if isinstance(o, Commit):
 
143
            rev = mapping.import_commit(o)
 
144
            root_trees[rev.revision_id] = object_iter[o.tree]
 
145
            revisions[rev.revision_id] = rev
 
146
            graph.append((rev.revision_id, rev.parent_ids))
 
147
    # Order the revisions
 
148
    # Create the inventory objects
 
149
    for i, revid in enumerate(topo_sort(graph)):
 
150
        if pb is not None:
 
151
            pb.update("fetching revisions", i, len(graph))
 
152
        root_tree = root_trees[revid]
 
153
        rev = revisions[revid]
 
154
        # We have to do this here, since we have to walk the tree and 
 
155
        # we need to make sure to import the blobs / trees with the riht 
 
156
        # path; this may involve adding them more than once.
 
157
        inv = Inventory()
 
158
        inv.revision_id = rev.revision_id
 
159
        def lookup_object(sha):
 
160
            if sha in object_iter:
 
161
                return object_iter[sha]
 
162
            return reconstruct_git_object(repo, mapping, sha)
 
163
        parent_invs = [repo.get_inventory(r) for r in rev.parent_ids]
 
164
        import_git_tree(repo, mapping, "", root_tree, inv, parent_invs, 
 
165
            lookup_object)
 
166
        repo.add_revision(rev.revision_id, rev, inv)
 
167
 
 
168
 
 
169
def reconstruct_git_commit(repo, rev):
 
170
    raise NotImplementedError(self.reconstruct_git_commit)
 
171
 
 
172
 
 
173
def reconstruct_git_object(repo, mapping, sha):
 
174
    # Commit
 
175
    revid = mapping.revision_id_foreign_to_bzr(sha)
 
176
    try:
 
177
        rev = repo.get_revision(revid)
 
178
    except NoSuchRevision:
 
179
        pass
 
180
    else:
 
181
        return reconstruct_git_commit(rev)
 
182
 
 
183
    # TODO: Tree
 
184
    # TODO: Blob
 
185
    raise KeyError("No such object %s" % sha)
 
186
 
 
187
 
 
188
class InterGitNonGitRepository(InterRepository):
 
189
 
 
190
    _matching_repo_format = GitFormat()
 
191
 
 
192
    @staticmethod
 
193
    def _get_repo_format_to_test():
 
194
        return None
 
195
 
 
196
    def copy_content(self, revision_id=None, pb=None):
 
197
        """See InterRepository.copy_content."""
 
198
        self.fetch(revision_id, pb, find_ghosts=False)
 
199
 
 
200
    def fetch(self, revision_id=None, pb=None, find_ghosts=False, 
 
201
              mapping=None):
 
202
        if mapping is None:
 
203
            mapping = self.source.get_mapping()
 
204
        def progress(text):
 
205
            pb.update("git: %s" % text.rstrip("\r\n"), 0, 0)
 
206
        def determine_wants(heads):
 
207
            if revision_id is None:
 
208
                ret = heads.values()
 
209
            else:
 
210
                ret = [mapping.revision_id_bzr_to_foreign(revision_id)[0]]
 
211
            return [rev for rev in ret if not self.target.has_revision(mapping.revision_id_foreign_to_bzr(rev))]
 
212
        graph_walker = BzrFetchGraphWalker(self.target, mapping)
 
213
        create_pb = None
 
214
        if pb is None:
 
215
            create_pb = pb = ui.ui_factory.nested_progress_bar()
 
216
        try:
 
217
            self.target.lock_write()
 
218
            try:
 
219
                self.target.start_write_group()
 
220
                try:
 
221
                    objects_iter = self.source.fetch_objects(determine_wants, 
 
222
                                graph_walker, progress)
 
223
                    import_git_objects(self.target, mapping, objects_iter, pb)
 
224
                finally:
 
225
                    self.target.commit_write_group()
 
226
            finally:
 
227
                self.target.unlock()
 
228
        finally:
 
229
            if create_pb:
 
230
                create_pb.finished()
 
231
 
 
232
    @staticmethod
 
233
    def is_compatible(source, target):
 
234
        """Be compatible with GitRepository."""
 
235
        # FIXME: Also check target uses VersionedFile
 
236
        return (isinstance(source, GitRepository) and 
 
237
                target.supports_rich_root() and
 
238
                not isinstance(target, GitRepository))
 
239
 
 
240
 
 
241
class InterGitRepository(InterRepository):
 
242
 
 
243
    _matching_repo_format = GitFormat()
 
244
 
 
245
    @staticmethod
 
246
    def _get_repo_format_to_test():
 
247
        return None
 
248
 
 
249
    def copy_content(self, revision_id=None, pb=None):
 
250
        """See InterRepository.copy_content."""
 
251
        self.fetch(revision_id, pb, find_ghosts=False)
 
252
 
 
253
    def fetch(self, revision_id=None, pb=None, find_ghosts=False, 
 
254
              mapping=None):
 
255
        if mapping is None:
 
256
            mapping = self.source.get_mapping()
 
257
        def progress(text):
 
258
            info("git: %s", text)
 
259
        r = self.target._git
 
260
        if revision_id is None:
 
261
            determine_wants = lambda x: [y for y in x.values() if not y in r.object_store]
 
262
        else:
 
263
            args = [mapping.revision_id_bzr_to_foreign(revision_id)[0]]
 
264
            determine_wants = lambda x: [y for y in args if not y in r.object_store]
 
265
 
 
266
        graphwalker = SimpleFetchGraphWalker(r.heads().values(), r.get_parents)
 
267
        f, commit = r.object_store.add_pack()
 
268
        try:
 
269
            self.source._git.fetch_pack(path, determine_wants, graphwalker, f.write, progress)
 
270
            f.close()
 
271
            commit()
 
272
        except:
 
273
            f.close()
 
274
            raise
 
275
 
 
276
    @staticmethod
 
277
    def is_compatible(source, target):
 
278
        """Be compatible with GitRepository."""
 
279
        return (isinstance(source, GitRepository) and 
 
280
                isinstance(target, GitRepository))