/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 Jelmer Vernooij <jelmer@samba.org>
 
1
# Copyright (C) 2008 Canonical Ltd
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
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
16
 
17
 
from cStringIO import (
18
 
    StringIO,
19
 
    )
20
 
import dulwich as git
21
 
from dulwich.client import (
22
 
    SimpleFetchGraphWalker,
23
 
    )
24
 
from dulwich.objects import (
25
 
    Commit,
26
 
    Tag,
27
 
    )
28
 
 
29
 
from bzrlib import (
30
 
    osutils,
31
 
    trace,
32
 
    ui,
33
 
    urlutils,
34
 
    )
35
 
from bzrlib.errors import (
36
 
    InvalidRevisionId,
37
 
    NoSuchRevision,
38
 
    )
39
 
from bzrlib.inventory import (
40
 
    Inventory,
41
 
    InventoryDirectory,
42
 
    InventoryFile,
43
 
    InventoryLink,
44
 
    )
45
 
from bzrlib.lru_cache import (
46
 
    LRUCache,
47
 
    )
48
 
from bzrlib.repository import (
49
 
    InterRepository,
50
 
    )
51
 
from bzrlib.revision import (
52
 
    NULL_REVISION,
53
 
    )
54
 
from bzrlib.tsort import (
55
 
    topo_sort,
56
 
    )
57
 
 
58
 
from bzrlib.plugins.git.converter import (
59
 
    GitObjectConverter,
60
 
    )
 
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
61
25
from bzrlib.plugins.git.repository import (
62
 
    LocalGitRepository, 
63
 
    GitRepository, 
64
 
    GitRepositoryFormat,
65
 
    )
66
 
from bzrlib.plugins.git.remote import (
67
 
    RemoteGitRepository,
68
 
    )
 
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
69
35
 
70
36
 
71
37
class BzrFetchGraphWalker(object):
72
 
    """GraphWalker implementation that uses a Bazaar repository."""
73
38
 
74
39
    def __init__(self, repository, mapping):
75
40
        self.repository = repository
78
43
        self.heads = set(repository.all_revision_ids())
79
44
        self.parents = {}
80
45
 
81
 
    def __iter__(self):
82
 
        return iter(self.next, None)
83
 
 
84
46
    def ack(self, sha):
85
47
        revid = self.mapping.revision_id_foreign_to_bzr(sha)
86
48
        self.remove(revid)
87
49
 
88
50
    def remove(self, revid):
89
51
        self.done.add(revid)
90
 
        if revid in self.heads:
 
52
        if ref in self.heads:
91
53
            self.heads.remove(revid)
92
54
        if revid in self.parents:
93
55
            for p in self.parents[revid]:
101
63
            self.heads.update([p for p in ps if not p in self.done])
102
64
            try:
103
65
                self.done.add(ret)
104
 
                return self.mapping.revision_id_bzr_to_foreign(ret)[0]
 
66
                return self.mapping.revision_id_bzr_to_foreign(ret)
105
67
            except InvalidRevisionId:
106
68
                pass
107
69
        return None
108
70
 
109
71
 
110
 
def import_git_blob(texts, mapping, path, blob, base_inv, parent_id, 
111
 
    revision_id, parent_invs, shagitmap, executable):
 
72
def import_git_blob(repo, mapping, path, blob, inv, parent_invs, executable):
112
73
    """Import a git blob object into a bzr repository.
113
74
 
114
 
    :param texts: VersionedFiles to add to
 
75
    :param repo: bzr repository
115
76
    :param path: Path in the tree
116
77
    :param blob: A git blob
117
 
    :return: Inventory delta for this file
118
78
    """
119
79
    file_id = mapping.generate_file_id(path)
120
 
    # We just have to hope this is indeed utf-8:
121
 
    ie = InventoryFile(file_id, urlutils.basename(path).decode("utf-8"), 
122
 
        parent_id)
 
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
123
86
    ie.text_size = len(blob.data)
124
87
    ie.text_sha1 = osutils.sha_string(blob.data)
125
88
    ie.executable = executable
126
 
    # If there were no changes compared to the base inventory, there's no need 
127
 
    # for a delta
128
 
    if (file_id in base_inv and 
129
 
        base_inv[file_id].parent_id == ie.parent_id and
130
 
        base_inv[file_id].text_sha1 == ie.text_sha1 and
131
 
        base_inv[file_id].executable == ie.executable):
132
 
        return []
133
 
    # Check what revision we should store
134
 
    parent_keys = []
135
 
    for pinv in parent_invs:
136
 
        if not file_id in pinv:
137
 
            continue
138
 
        if pinv[file_id].text_sha1 == ie.text_sha1:
139
 
            # found a revision in one of the parents to use
140
 
            ie.revision = pinv[file_id].revision
141
 
            break
142
 
        parent_keys.append((file_id, pinv[file_id].revision))
143
 
    if ie.revision is None:
144
 
        # Need to store a new revision
145
 
        ie.revision = revision_id
146
 
        assert file_id is not None
147
 
        assert ie.revision is not None
148
 
        texts.add_lines((file_id, ie.revision), parent_keys,
149
 
            osutils.split_lines(blob.data))
150
 
        shagitmap.add_entry(blob.sha().hexdigest(), "blob",
151
 
            (ie.file_id, ie.revision))
152
 
    if file_id in base_inv:
153
 
        old_path = base_inv.id2path(file_id)
154
 
    else:
155
 
        old_path = None
156
 
    return [(old_path, path, file_id, ie)]
157
 
 
158
 
 
159
 
def import_git_tree(texts, mapping, path, tree, base_inv, parent_id, 
160
 
    revision_id, parent_invs, shagitmap, lookup_object):
 
89
 
 
90
 
 
91
def import_git_tree(repo, mapping, path, tree, inv, parent_invs, lookup_object):
161
92
    """Import a git tree object into a bzr repository.
162
93
 
163
 
    :param texts: VersionedFiles object to add to
 
94
    :param repo: A Bzr repository object
164
95
    :param path: Path in the tree
165
96
    :param tree: A git tree object
166
 
    :param base_inv: Base inventory against which to return inventory delta
167
 
    :return: Inventory delta for this subtree
 
97
    :param inv: Inventory object
168
98
    """
169
 
    ret = []
170
99
    file_id = mapping.generate_file_id(path)
171
 
    # We just have to hope this is indeed utf-8:
172
 
    ie = InventoryDirectory(file_id, urlutils.basename(path.decode("utf-8")), 
173
 
        parent_id)
174
 
    if not file_id in base_inv:
175
 
        # Newly appeared here
176
 
        ie.revision = revision_id
177
 
        texts.add_lines((file_id, ie.revision), [], [])
178
 
        ret.append((None, path, file_id, ie))
179
 
    else:
180
 
        # See if this has changed at all
181
 
        try:
182
 
            base_sha = shagitmap.lookup_tree(path, base_inv.revision_id)
183
 
        except KeyError:
184
 
            pass
185
 
        else:
186
 
            if base_sha == tree.id:
187
 
                # If nothing has changed since the base revision, we're done
188
 
                return []
189
 
    # Remember for next time
190
 
    existing_children = set()
191
 
    shagitmap.add_entry(tree.id, "tree", (file_id, revision_id))
 
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
192
106
    for mode, name, hexsha in tree.entries():
193
107
        entry_kind = (mode & 0700000) / 0100000
194
108
        basename = name.decode("utf-8")
195
 
        existing_children.add(basename)
196
109
        if path == "":
197
110
            child_path = name
198
111
        else:
199
112
            child_path = urlutils.join(path, name)
200
 
        obj = lookup_object(hexsha)
201
113
        if entry_kind == 0:
202
 
            ret.extend(import_git_tree(texts, mapping, child_path, obj, base_inv, 
203
 
                file_id, revision_id, parent_invs, shagitmap, lookup_object))
 
114
            tree = lookup_object(hexsha)
 
115
            import_git_tree(repo, mapping, child_path, tree, inv, parent_invs, lookup_object)
204
116
        elif entry_kind == 1:
 
117
            blob = lookup_object(hexsha)
205
118
            fs_mode = mode & 0777
206
 
            ret.extend(import_git_blob(texts, mapping, child_path, obj, base_inv, 
207
 
                file_id, revision_id, parent_invs, shagitmap, 
208
 
                bool(fs_mode & 0111)))
 
119
            import_git_blob(repo, mapping, child_path, blob, inv, parent_invs, bool(fs_mode & 0111))
209
120
        else:
210
121
            raise AssertionError("Unknown blob kind, perms=%r." % (mode,))
211
 
    # Remove any children that have disappeared
212
 
    if file_id in base_inv:
213
 
        deletable = [v for k,v in base_inv[file_id].children.iteritems() if k not in existing_children]
214
 
        while deletable:
215
 
            ie = deletable.pop()
216
 
            ret.append((base_inv.id2path(ie.file_id), None, ie.file_id, None))
217
 
            if ie.kind == "directory":
218
 
                deletable.extend(ie.children.values())
219
 
    return ret
220
 
 
221
 
 
222
 
def import_git_objects(repo, mapping, object_iter, target_git_object_retriever, 
223
 
        heads, pb=None):
 
122
 
 
123
 
 
124
def import_git_objects(repo, mapping, object_iter, pb=None):
224
125
    """Import a set of git objects into a bzr repository.
225
126
 
226
127
    :param repo: Bazaar repository
228
129
    :param object_iter: Iterator over Git objects.
229
130
    """
230
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
231
137
    graph = []
232
138
    root_trees = {}
233
139
    revisions = {}
234
 
    checked = set()
235
 
    heads = list(heads)
236
 
    parent_invs_cache = LRUCache(50)
237
140
    # Find and convert commit objects
238
 
    while heads:
239
 
        if pb is not None:
240
 
            pb.update("finding revisions to fetch", len(graph), None)
241
 
        head = heads.pop()
242
 
        assert isinstance(head, str)
243
 
        o = object_iter[head]
 
141
    for o in objects.itervalues():
244
142
        if isinstance(o, Commit):
245
143
            rev = mapping.import_commit(o)
246
 
            if repo.has_revision(rev.revision_id):
247
 
                continue
248
 
            root_trees[rev.revision_id] = o.tree
 
144
            root_trees[rev.revision_id] = objects[o.tree]
249
145
            revisions[rev.revision_id] = rev
250
146
            graph.append((rev.revision_id, rev.parent_ids))
251
 
            target_git_object_retriever._idmap.add_entry(o.sha().hexdigest(),
252
 
                "commit", (rev.revision_id, o._tree))
253
 
            heads.extend([p for p in o.parents if p not in checked])
254
 
        elif isinstance(o, Tag):
255
 
            heads.append(o.object[1])
256
 
        else:
257
 
            trace.warning("Unable to import head object %r" % o)
258
 
        checked.add(head)
259
147
    # Order the revisions
260
148
    # Create the inventory objects
261
149
    for i, revid in enumerate(topo_sort(graph)):
262
150
        if pb is not None:
263
151
            pb.update("fetching revisions", i, len(graph))
264
 
        root_tree = object_iter[root_trees[revid]]
 
152
        root_tree = root_trees[revid]
265
153
        rev = revisions[revid]
266
154
        # We have to do this here, since we have to walk the tree and 
267
 
        # we need to make sure to import the blobs / trees with the right 
 
155
        # we need to make sure to import the blobs / trees with the riht 
268
156
        # path; this may involve adding them more than once.
 
157
        inv = Inventory()
 
158
        inv.revision_id = rev.revision_id
269
159
        def lookup_object(sha):
270
 
            try:
271
 
                return object_iter[sha]
272
 
            except KeyError:
273
 
                return target_git_object_retriever[sha]
274
 
        parent_invs = []
275
 
        for parent_id in rev.parent_ids:
276
 
            try:
277
 
                parent_invs.append(parent_invs_cache[parent_id])
278
 
            except KeyError:
279
 
                parent_inv = repo.get_inventory(parent_id)
280
 
                parent_invs.append(parent_inv)
281
 
                parent_invs_cache[parent_id] = parent_inv
282
 
        if parent_invs == []:
283
 
            base_inv = Inventory(root_id=None)
284
 
        else:
285
 
            base_inv = parent_invs[0]
286
 
        inv_delta = import_git_tree(repo.texts, mapping, "", root_tree, 
287
 
            base_inv, None, revid, parent_invs, 
288
 
            target_git_object_retriever._idmap, lookup_object)
289
 
        try:
290
 
            basis_id = rev.parent_ids[0]
291
 
        except IndexError:
292
 
            basis_id = NULL_REVISION
293
 
        rev.inventory_sha1, inv = repo.add_inventory_by_delta(basis_id,
294
 
                  inv_delta, rev.revision_id, rev.parent_ids)
295
 
        parent_invs_cache[rev.revision_id] = inv
296
 
        repo.add_revision(rev.revision_id, rev)
297
 
    target_git_object_retriever._idmap.commit()
298
 
 
299
 
 
300
 
class InterGitNonGitRepository(InterRepository):
301
 
    """InterRepository that copies revisions from a Git into a non-Git 
302
 
    repository."""
303
 
 
304
 
    _matching_repo_format = GitRepositoryFormat()
 
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()
305
190
 
306
191
    @staticmethod
307
192
    def _get_repo_format_to_test():
311
196
        """See InterRepository.copy_content."""
312
197
        self.fetch(revision_id, pb, find_ghosts=False)
313
198
 
314
 
    def fetch_objects(self, determine_wants, mapping, pb=None):
 
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()
315
203
        def progress(text):
316
 
            pb.update("git: %s" % text.rstrip("\r\n"), 0, 0)
 
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))]
317
211
        graph_walker = BzrFetchGraphWalker(self.target, mapping)
318
212
        create_pb = None
319
213
        if pb is None:
320
214
            create_pb = pb = ui.ui_factory.nested_progress_bar()
321
 
        target_git_object_retriever = GitObjectConverter(self.target, mapping)
322
 
        recorded_wants = []
323
 
 
324
 
        def record_determine_wants(heads):
325
 
            wants = determine_wants(heads)
326
 
            recorded_wants.extend(wants)
327
 
            return wants
328
 
        
329
215
        try:
330
216
            self.target.lock_write()
331
217
            try:
332
218
                self.target.start_write_group()
333
219
                try:
334
 
                    objects_iter = self.source.fetch_objects(
335
 
                                record_determine_wants, 
336
 
                                graph_walker, 
337
 
                                target_git_object_retriever.__getitem__, 
338
 
                                progress)
339
 
                    import_git_objects(self.target, mapping, objects_iter, 
340
 
                            target_git_object_retriever, recorded_wants, pb)
 
220
                    import_git_objects(self.target, mapping,
 
221
                        iter(self.source.fetch_objects(determine_wants, graph_walker, 
 
222
                            progress)), pb)
341
223
                finally:
342
224
                    self.target.commit_write_group()
343
225
            finally:
346
228
            if create_pb:
347
229
                create_pb.finished()
348
230
 
349
 
    def fetch(self, revision_id=None, pb=None, find_ghosts=False, mapping=None,
350
 
            fetch_spec=None):
351
 
        self.fetch_refs(revision_id=revision_id, pb=pb, find_ghosts=find_ghosts,
352
 
                mapping=mapping, fetch_spec=fetch_spec)
353
 
 
354
 
    def fetch_refs(self, revision_id=None, pb=None, find_ghosts=False, 
355
 
              mapping=None, fetch_spec=None):
356
 
        if mapping is None:
357
 
            mapping = self.source.get_mapping()
358
 
        if revision_id is not None:
359
 
            interesting_heads = [revision_id]
360
 
        elif fetch_spec is not None:
361
 
            interesting_heads = fetch_spec.heads
362
 
        else:
363
 
            interesting_heads = None
364
 
        self._refs = {}
365
 
        def determine_wants(refs):
366
 
            self._refs = refs
367
 
            if interesting_heads is None:
368
 
                ret = [sha for (ref, sha) in refs.iteritems() if not ref.endswith("^{}")]
369
 
            else:
370
 
                ret = [mapping.revision_id_bzr_to_foreign(revid)[0] for revid in interesting_heads]
371
 
            return [rev for rev in ret if not self.target.has_revision(mapping.revision_id_foreign_to_bzr(rev))]
372
 
        self.fetch_objects(determine_wants, mapping, pb)
373
 
        return self._refs
374
 
 
375
231
    @staticmethod
376
232
    def is_compatible(source, target):
377
233
        """Be compatible with GitRepository."""
378
234
        # FIXME: Also check target uses VersionedFile
379
235
        return (isinstance(source, GitRepository) and 
380
 
                target.supports_rich_root() and
381
 
                not isinstance(target, GitRepository))
382
 
 
383
 
 
384
 
class InterGitRepository(InterRepository):
385
 
    """InterRepository that copies between Git repositories."""
386
 
 
387
 
    _matching_repo_format = GitRepositoryFormat()
388
 
 
389
 
    @staticmethod
390
 
    def _get_repo_format_to_test():
391
 
        return None
392
 
 
393
 
    def copy_content(self, revision_id=None, pb=None):
394
 
        """See InterRepository.copy_content."""
395
 
        self.fetch(revision_id, pb, find_ghosts=False)
396
 
 
397
 
    def fetch(self, revision_id=None, pb=None, find_ghosts=False, 
398
 
              mapping=None, fetch_spec=None):
399
 
        if mapping is None:
400
 
            mapping = self.source.get_mapping()
401
 
        def progress(text):
402
 
            trace.info("git: %s", text)
403
 
        r = self.target._git
404
 
        if revision_id is not None:
405
 
            args = [mapping.revision_id_bzr_to_foreign(revision_id)[0]]
406
 
        elif fetch_spec is not None:
407
 
            args = [mapping.revision_id_bzr_to_foreign(revid)[0] for revid in fetch_spec.heads]
408
 
        if fetch_spec is None and revision_id is None:
409
 
            determine_wants = r.object_store.determine_wants_all
410
 
        else:
411
 
            determine_wants = lambda x: [y for y in args if not y in r.object_store]
412
 
 
413
 
        graphwalker = SimpleFetchGraphWalker(r.heads().values(), r.get_parents)
414
 
        f, commit = r.object_store.add_pack()
415
 
        try:
416
 
            self.source._git.fetch_pack(path, determine_wants, graphwalker, f.write, progress)
417
 
            f.close()
418
 
            commit()
419
 
        except:
420
 
            f.close()
421
 
            raise
422
 
 
423
 
    @staticmethod
424
 
    def is_compatible(source, target):
425
 
        """Be compatible with GitRepository."""
426
 
        return (isinstance(source, GitRepository) and 
427
 
                isinstance(target, GitRepository))
 
236
                target.supports_rich_root())