/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

Merge new bzr-foreign.

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