/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

Fix branch cloning.

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.objects import Commit
 
33
 
 
34
from cStringIO import StringIO
 
35
 
 
36
 
 
37
class BzrFetchGraphWalker(object):
 
38
 
 
39
    def __init__(self, repository, mapping):
 
40
        self.repository = repository
 
41
        self.mapping = mapping
 
42
        self.done = set()
 
43
        self.heads = set(repository.all_revision_ids())
 
44
        self.parents = {}
 
45
 
 
46
    def ack(self, sha):
 
47
        revid = self.mapping.revision_id_foreign_to_bzr(sha)
 
48
        self.remove(revid)
 
49
 
 
50
    def remove(self, revid):
 
51
        self.done.add(revid)
 
52
        if ref in self.heads:
 
53
            self.heads.remove(revid)
 
54
        if revid in self.parents:
 
55
            for p in self.parents[revid]:
 
56
                self.remove(p)
 
57
 
 
58
    def next(self):
 
59
        while self.heads:
 
60
            ret = self.heads.pop()
 
61
            ps = self.repository.get_parent_map([ret])[ret]
 
62
            self.parents[ret] = ps
 
63
            self.heads.update([p for p in ps if not p in self.done])
 
64
            try:
 
65
                self.done.add(ret)
 
66
                return self.mapping.revision_id_bzr_to_foreign(ret)
 
67
            except InvalidRevisionId:
 
68
                pass
 
69
        return None
 
70
 
 
71
 
 
72
def import_git_blob(repo, mapping, path, blob, inv, parent_invs, executable):
 
73
    """Import a git blob object into a bzr repository.
 
74
 
 
75
    :param repo: bzr repository
 
76
    :param path: Path in the tree
 
77
    :param blob: A git blob
 
78
    """
 
79
    file_id = mapping.generate_file_id(path)
 
80
    text_revision = inv.revision_id
 
81
    repo.texts.add_lines((file_id, text_revision),
 
82
        [(file_id, p[file_id].revision) for p in parent_invs if file_id in p],
 
83
        osutils.split_lines(blob.data))
 
84
    ie = inv.add_path(path, "file", file_id)
 
85
    ie.revision = text_revision
 
86
    ie.text_size = len(blob.data)
 
87
    ie.text_sha1 = osutils.sha_string(blob.data)
 
88
    ie.executable = executable
 
89
 
 
90
 
 
91
def import_git_tree(repo, mapping, path, tree, inv, parent_invs, lookup_object):
 
92
    """Import a git tree object into a bzr repository.
 
93
 
 
94
    :param repo: A Bzr repository object
 
95
    :param path: Path in the tree
 
96
    :param tree: A git tree object
 
97
    :param inv: Inventory object
 
98
    """
 
99
    file_id = mapping.generate_file_id(path)
 
100
    text_revision = inv.revision_id
 
101
    repo.texts.add_lines((file_id, text_revision),
 
102
        [(file_id, p[file_id].revision) for p in parent_invs if file_id in p],
 
103
        [])
 
104
    ie = inv.add_path(path, "directory", file_id)
 
105
    ie.revision = text_revision
 
106
    for mode, name, hexsha in tree.entries():
 
107
        entry_kind = (mode & 0700000) / 0100000
 
108
        basename = name.decode("utf-8")
 
109
        if path == "":
 
110
            child_path = name
 
111
        else:
 
112
            child_path = urlutils.join(path, name)
 
113
        if entry_kind == 0:
 
114
            tree = lookup_object(hexsha)
 
115
            import_git_tree(repo, mapping, child_path, tree, inv, parent_invs, lookup_object)
 
116
        elif entry_kind == 1:
 
117
            blob = lookup_object(hexsha)
 
118
            fs_mode = mode & 0777
 
119
            import_git_blob(repo, mapping, child_path, blob, inv, parent_invs, bool(fs_mode & 0111))
 
120
        else:
 
121
            raise AssertionError("Unknown blob kind, perms=%r." % (mode,))
 
122
 
 
123
 
 
124
def import_git_objects(repo, mapping, object_iter, pb=None):
 
125
    """Import a set of git objects into a bzr repository.
 
126
 
 
127
    :param repo: Bazaar repository
 
128
    :param mapping: Mapping to use
 
129
    :param object_iter: Iterator over Git objects.
 
130
    """
 
131
    # TODO: a more (memory-)efficient implementation of this
 
132
    objects = {}
 
133
    for i, o in enumerate(object_iter):
 
134
        if pb is not None:
 
135
            pb.update("fetching objects", i) 
 
136
        objects[o.id] = o
 
137
    graph = []
 
138
    root_trees = {}
 
139
    revisions = {}
 
140
    # Find and convert commit objects
 
141
    for o in objects.itervalues():
 
142
        if isinstance(o, Commit):
 
143
            rev = mapping.import_commit(o)
 
144
            root_trees[rev.revision_id] = objects[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 objects:
 
161
                return objects[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, lookup_object)
 
165
        repo.add_revision(rev.revision_id, rev, inv)
 
166
 
 
167
 
 
168
def reconstruct_git_commit(repo, rev):
 
169
    raise NotImplementedError(self.reconstruct_git_commit)
 
170
 
 
171
 
 
172
def reconstruct_git_object(repo, mapping, sha):
 
173
    # Commit
 
174
    revid = mapping.revision_id_foreign_to_bzr(sha)
 
175
    try:
 
176
        rev = repo.get_revision(revid)
 
177
    except NoSuchRevision:
 
178
        pass
 
179
    else:
 
180
        return reconstruct_git_commit(rev)
 
181
 
 
182
    # TODO: Tree
 
183
    # TODO: Blob
 
184
    raise KeyError("No such object %s" % sha)
 
185
 
 
186
 
 
187
class InterGitRepository(InterRepository):
 
188
 
 
189
    _matching_repo_format = GitFormat()
 
190
 
 
191
    @staticmethod
 
192
    def _get_repo_format_to_test():
 
193
        return None
 
194
 
 
195
    def copy_content(self, revision_id=None, pb=None):
 
196
        """See InterRepository.copy_content."""
 
197
        self.fetch(revision_id, pb, find_ghosts=False)
 
198
 
 
199
    def fetch(self, revision_id=None, pb=None, find_ghosts=False, 
 
200
              mapping=None):
 
201
        if mapping is None:
 
202
            mapping = self.source.get_mapping()
 
203
        def progress(text):
 
204
            pb.note("git: %s", text)
 
205
        def determine_wants(heads):
 
206
            if revision_id is None:
 
207
                ret = heads.values()
 
208
            else:
 
209
                ret = [mapping.revision_id_bzr_to_foreign(revision_id)]
 
210
            return [rev for rev in ret if not self.target.has_revision(mapping.revision_id_foreign_to_bzr(rev))]
 
211
        graph_walker = BzrFetchGraphWalker(self.target, mapping)
 
212
        create_pb = None
 
213
        if pb is None:
 
214
            create_pb = pb = ui.ui_factory.nested_progress_bar()
 
215
        try:
 
216
            self.target.lock_write()
 
217
            try:
 
218
                self.target.start_write_group()
 
219
                try:
 
220
                    import_git_objects(self.target, mapping,
 
221
                        iter(self.source.fetch_objects(determine_wants, graph_walker, 
 
222
                            progress)), pb)
 
223
                finally:
 
224
                    self.target.commit_write_group()
 
225
            finally:
 
226
                self.target.unlock()
 
227
        finally:
 
228
            if create_pb:
 
229
                create_pb.finished()
 
230
 
 
231
    @staticmethod
 
232
    def is_compatible(source, target):
 
233
        """Be compatible with GitRepository."""
 
234
        # FIXME: Also check target uses VersionedFile
 
235
        return (isinstance(source, GitRepository) and 
 
236
                target.supports_rich_root())