/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

mention the requirement to install Dulwich.

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 dulwich.objects import (
18
 
    Commit,
19
 
    Tag,
20
 
    Tree,
21
 
    S_ISGITLINK,
22
 
    )
23
 
from dulwich.object_store import (
24
 
    tree_lookup_path,
25
 
    )
26
 
from itertools import (
27
 
    imap,
28
 
    )
29
 
import posixpath
30
 
import re
31
 
import stat
32
 
 
33
 
from bzrlib import (
34
 
    debug,
35
 
    osutils,
36
 
    trace,
37
 
    ui,
38
 
    )
39
 
from bzrlib.errors import (
40
 
    BzrError,
41
 
    NoSuchId,
42
 
    )
43
 
from bzrlib.inventory import (
44
 
    Inventory,
45
 
    InventoryDirectory,
46
 
    InventoryFile,
47
 
    InventoryLink,
48
 
    TreeReference,
49
 
    )
50
 
from bzrlib.repository import (
51
 
    InterRepository,
52
 
    )
53
 
from bzrlib.revision import (
54
 
    NULL_REVISION,
55
 
    )
56
 
from bzrlib.tsort import (
57
 
    topo_sort,
58
 
    )
59
 
from bzrlib.versionedfile import (
60
 
    ChunkedContentFactory,
61
 
    )
62
 
 
63
 
from bzrlib.plugins.git.mapping import (
64
 
    DEFAULT_FILE_MODE,
65
 
    inventory_to_tree_and_blobs,
66
 
    mode_is_executable,
67
 
    mode_kind,
68
 
    squash_revision,
69
 
    warn_unusual_mode,
70
 
    )
71
 
from bzrlib.plugins.git.object_store import (
72
 
    BazaarObjectStore,
73
 
    LRUInventoryCache,
74
 
    )
75
 
from bzrlib.plugins.git.remote import (
76
 
    RemoteGitRepository,
77
 
    )
 
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
 
78
24
from bzrlib.plugins.git.repository import (
79
 
    GitRepository,
80
 
    GitRepositoryFormat,
81
 
    LocalGitRepository,
82
 
    )
83
 
 
84
 
 
85
 
def import_git_blob(texts, mapping, path, name, (base_hexsha, hexsha), 
86
 
        base_inv, parent_id, revision_id,
87
 
        parent_invs, lookup_object, (base_mode, mode), store_updater):
 
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):
88
78
    """Import a git blob object into a bzr repository.
89
79
 
90
 
    :param texts: VersionedFiles to add to
 
80
    :param repo: bzr repository
91
81
    :param path: Path in the tree
92
82
    :param blob: A git blob
93
 
    :return: Inventory delta for this file
94
83
    """
95
 
    if base_hexsha == hexsha and base_mode == mode:
96
 
        # If nothing has changed since the base revision, we're done
97
 
        return []
98
 
    file_id = mapping.generate_file_id(path)
99
 
    if stat.S_ISLNK(mode):
100
 
        cls = InventoryLink
101
 
    else:
102
 
        cls = InventoryFile
103
 
    ie = cls(file_id, name.decode("utf-8"), parent_id)
104
 
    ie.executable = mode_is_executable(mode)
105
 
    if base_hexsha == hexsha and mode_kind(base_mode) == mode_kind(mode):
106
 
        base_ie = base_inv[base_inv.path2id(path)]
107
 
        ie.text_size = base_ie.text_size
108
 
        ie.text_sha1 = base_ie.text_sha1
109
 
        ie.symlink_target = base_ie.symlink_target
110
 
        if ie.executable == base_ie.executable:
111
 
            ie.revision = base_ie.revision
112
 
        else:
113
 
            blob = lookup_object(hexsha)
114
 
    else:
115
 
        blob = lookup_object(hexsha)
116
 
        if ie.kind == "symlink":
117
 
            ie.revision = None
118
 
            ie.symlink_target = blob.data
119
 
            ie.text_size = None
120
 
            ie.text_sha1 = None
121
 
        else:
122
 
            ie.text_size = sum(imap(len, blob.chunked))
123
 
            ie.text_sha1 = osutils.sha_strings(blob.chunked)
124
 
    # Check what revision we should store
125
 
    parent_keys = []
126
 
    for pinv in parent_invs[1:]:
127
 
        try:
128
 
            pie = pinv[file_id]
129
 
        except NoSuchId:
130
 
            continue
131
 
        if pie.text_sha1 == ie.text_sha1 and pie.executable == ie.executable and pie.symlink_target == ie.symlink_target:
132
 
            # found a revision in one of the parents to use
133
 
            ie.revision = pie.revision
134
 
            break
135
 
        parent_keys.append((file_id, pie.revision))
136
 
    if ie.revision is None:
137
 
        # Need to store a new revision
138
 
        ie.revision = revision_id
139
 
        assert ie.revision is not None
140
 
        if ie.kind == 'symlink':
141
 
            chunks = []
142
 
        else: 
143
 
            chunks = blob.chunked
144
 
        texts.insert_record_stream([ChunkedContentFactory((file_id, ie.revision), tuple(parent_keys), ie.text_sha1, chunks)])
145
 
    invdelta = []
146
 
    if base_hexsha is not None:
147
 
        old_path = path # Renames are not supported yet
148
 
        if stat.S_ISDIR(base_mode):
149
 
            invdelta.extend(remove_disappeared_children(base_inv, old_path,
150
 
                lookup_object(base_hexsha), [], lookup_object))
151
 
    else:
152
 
        old_path = None
153
 
    invdelta.append((old_path, path, file_id, ie))
154
 
    if base_hexsha != hexsha:
155
 
        store_updater.add_object(blob, ie)
156
 
    return invdelta
157
 
 
158
 
 
159
 
class SubmodulesRequireSubtrees(BzrError):
160
 
    _fmt = """The repository you are fetching from contains submodules. To continue, upgrade your Bazaar repository to a format that supports nested trees, such as 'development-subtree'."""
161
 
    internal = False
162
 
 
163
 
 
164
 
def import_git_submodule(texts, mapping, path, name, (base_hexsha, hexsha),
165
 
    base_inv, parent_id, revision_id, parent_invs, lookup_object,
166
 
    (base_mode, mode), store_updater):
167
 
    if base_hexsha == hexsha and base_mode == mode:
168
 
        return [], {}
169
 
    file_id = mapping.generate_file_id(path)
170
 
    ie = TreeReference(file_id, name.decode("utf-8"), parent_id)
171
 
    ie.revision = revision_id
172
 
    if base_hexsha is None:
173
 
        oldpath = None
174
 
    else:
175
 
        oldpath = path
176
 
    ie.reference_revision = mapping.revision_id_foreign_to_bzr(hexsha)
177
 
    texts.insert_record_stream([ChunkedContentFactory((file_id, ie.revision), (), None, [])])
178
 
    invdelta = [(oldpath, path, file_id, ie)]
179
 
    return invdelta, {}
180
 
 
181
 
 
182
 
def remove_disappeared_children(base_inv, path, base_tree, existing_children,
183
 
        lookup_object):
184
 
    ret = []
185
 
    for name, mode, hexsha in base_tree.iteritems():
186
 
        if name in existing_children:
187
 
            continue
188
 
        c_path = posixpath.join(path, name.decode("utf-8"))
189
 
        ret.append((c_path, None, base_inv.path2id(c_path), None))
190
 
        if stat.S_ISDIR(mode):
191
 
            ret.extend(remove_disappeared_children(
192
 
                base_inv, c_path, lookup_object(hexsha), [], lookup_object))
193
 
    return ret
194
 
 
195
 
 
196
 
def import_git_tree(texts, mapping, path, name, (base_hexsha, hexsha),
197
 
        base_inv, parent_id, revision_id, parent_invs,
198
 
    lookup_object, (base_mode, mode), store_updater, allow_submodules=False):
 
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):
199
97
    """Import a git tree object into a bzr repository.
200
98
 
201
 
    :param texts: VersionedFiles object to add to
 
99
    :param repo: A Bzr repository object
202
100
    :param path: Path in the tree
203
101
    :param tree: A git tree object
204
 
    :param base_inv: Base inventory against which to return inventory delta
205
 
    :return: Inventory delta for this subtree
 
102
    :param inv: Inventory object
206
103
    """
207
 
    if base_hexsha == hexsha and base_mode == mode:
208
 
        # If nothing has changed since the base revision, we're done
209
 
        return [], {}
210
 
    invdelta = []
211
104
    file_id = mapping.generate_file_id(path)
212
 
    # We just have to hope this is indeed utf-8:
213
 
    ie = InventoryDirectory(file_id, name.decode("utf-8"), parent_id)
214
 
    tree = lookup_object(hexsha)
215
 
    if base_hexsha is None:
216
 
        base_tree = None
217
 
        old_path = None # Newly appeared here
218
 
    else:
219
 
        base_tree = lookup_object(base_hexsha)
220
 
        old_path = path # Renames aren't supported yet
221
 
    if base_tree is None or type(base_tree) is not Tree:
222
 
        ie.revision = revision_id
223
 
        invdelta.append((old_path, path, ie.file_id, ie))
224
 
        texts.insert_record_stream([ChunkedContentFactory((ie.file_id, ie.revision), (), None, [])])
225
 
    # Remember for next time
226
 
    existing_children = set()
227
 
    child_modes = {}
228
 
    for child_mode, name, child_hexsha in tree.entries():
229
 
        existing_children.add(name)
230
 
        child_path = posixpath.join(path, name)
231
 
        if type(base_tree) is Tree:
232
 
            try:
233
 
                child_base_mode, child_base_hexsha = base_tree[name]
234
 
            except KeyError:
235
 
                child_base_hexsha = None
236
 
                child_base_mode = 0
237
 
        else:
238
 
            child_base_hexsha = None
239
 
            child_base_mode = 0
240
 
        if stat.S_ISDIR(child_mode):
241
 
            subinvdelta, grandchildmodes = import_git_tree(
242
 
                    texts, mapping, child_path, name,
243
 
                    (child_base_hexsha, child_hexsha),
244
 
                    base_inv, file_id, revision_id, parent_invs, lookup_object,
245
 
                    (child_base_mode, child_mode), store_updater,
246
 
                    allow_submodules=allow_submodules)
247
 
        elif S_ISGITLINK(child_mode): # submodule
248
 
            if not allow_submodules:
249
 
                raise SubmodulesRequireSubtrees()
250
 
            subinvdelta, grandchildmodes = import_git_submodule(
251
 
                    texts, mapping, child_path, name,
252
 
                    (child_base_hexsha, child_hexsha),
253
 
                    base_inv, file_id, revision_id, parent_invs, lookup_object,
254
 
                    (child_base_mode, child_mode), store_updater)
255
 
        else:
256
 
            subinvdelta = import_git_blob(texts, mapping,
257
 
                    child_path, name, (child_base_hexsha, child_hexsha),
258
 
                    base_inv, file_id, revision_id, parent_invs, lookup_object,
259
 
                    (child_base_mode, child_mode), store_updater)
260
 
            grandchildmodes = {}
261
 
        child_modes.update(grandchildmodes)
262
 
        invdelta.extend(subinvdelta)
263
 
        if child_mode not in (stat.S_IFDIR, DEFAULT_FILE_MODE,
264
 
                        stat.S_IFLNK, DEFAULT_FILE_MODE|0111):
265
 
            child_modes[child_path] = child_mode
266
 
    # Remove any children that have disappeared
267
 
    if base_tree is not None and type(base_tree) is Tree:
268
 
        invdelta.extend(remove_disappeared_children(base_inv, old_path, 
269
 
            base_tree, existing_children, lookup_object))
270
 
    store_updater.add_object(tree, ie)
271
 
    return invdelta, child_modes
272
 
 
273
 
 
274
 
def import_git_commit(repo, mapping, head, lookup_object,
275
 
                      target_git_object_retriever, parent_invs_cache):
276
 
    o = lookup_object(head)
277
 
    rev = mapping.import_commit(o)
278
 
    # We have to do this here, since we have to walk the tree and
279
 
    # we need to make sure to import the blobs / trees with the right
280
 
    # path; this may involve adding them more than once.
281
 
    parent_invs = parent_invs_cache.get_inventories(rev.parent_ids)
282
 
    if parent_invs == []:
283
 
        base_inv = Inventory(root_id=None)
284
 
        base_tree = None
285
 
        base_mode = None
286
 
    else:
287
 
        base_inv = parent_invs[0]
288
 
        base_tree = lookup_object(o.parents[0]).tree
289
 
        base_mode = stat.S_IFDIR
290
 
    store_updater = target_git_object_retriever._get_updater(rev)
291
 
    store_updater.add_object(o, None)
292
 
    inv_delta, unusual_modes = import_git_tree(repo.texts,
293
 
            mapping, "", u"", (base_tree, o.tree), base_inv, 
294
 
            None, rev.revision_id, parent_invs, lookup_object,
295
 
            (base_mode, stat.S_IFDIR), store_updater,
296
 
            allow_submodules=getattr(repo._format, "supports_tree_reference", False))
297
 
    store_updater.finish()
298
 
    if unusual_modes != {}:
299
 
        for path, mode in unusual_modes.iteritems():
300
 
            warn_unusual_mode(rev.foreign_revid, path, mode)
301
 
        mapping.import_unusual_file_modes(rev, unusual_modes)
302
 
    try:
303
 
        basis_id = rev.parent_ids[0]
304
 
    except IndexError:
305
 
        basis_id = NULL_REVISION
306
 
        base_inv = None
307
 
    rev.inventory_sha1, inv = repo.add_inventory_by_delta(basis_id,
308
 
              inv_delta, rev.revision_id, rev.parent_ids,
309
 
              base_inv)
310
 
    parent_invs_cache.add(rev.revision_id, inv)
311
 
    repo.add_revision(rev.revision_id, rev)
312
 
    if "verify" in debug.debug_flags:
313
 
        new_unusual_modes = mapping.export_unusual_file_modes(rev)
314
 
        if new_unusual_modes != unusual_modes:
315
 
            raise AssertionError("unusual modes don't match: %r != %r" % (unusual_modes, new_unusual_modes))
316
 
        objs = inventory_to_tree_and_blobs(inv, repo.texts, mapping, unusual_modes)
317
 
        for newsha1, newobj, path in objs:
318
 
            assert path is not None
319
 
            if path == "":
320
 
                oldsha1 = o.tree
321
 
            else:
322
 
                (oldmode, oldsha1) = tree_lookup_path(lookup_object, o.tree, path)
323
 
            if oldsha1 != newsha1:
324
 
                raise AssertionError("%r != %r in %s" % (oldsha1, newsha1, path))
325
 
 
326
 
 
327
 
def import_git_objects(repo, mapping, object_iter,
328
 
    target_git_object_retriever, heads, pb=None, limit=None):
 
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, num_objects, object_iter, pb=None):
329
130
    """Import a set of git objects into a bzr repository.
330
131
 
331
 
    :param repo: Target Bazaar repository
 
132
    :param repo: Bazaar repository
332
133
    :param mapping: Mapping to use
 
134
    :param num_objects: Number of objects.
333
135
    :param object_iter: Iterator over Git objects.
334
 
    :return: Tuple with pack hints and last imported revision id
335
136
    """
336
 
    def lookup_object(sha):
337
 
        try:
338
 
            return object_iter[sha]
339
 
        except KeyError:
340
 
            return target_git_object_retriever[sha]
 
137
    # TODO: a more (memory-)efficient implementation of this
 
138
    objects = {}
 
139
    for i, (o, _) in enumerate(object_iter):
 
140
        if pb is not None:
 
141
            pb.update("fetching objects", i, num_objects) 
 
142
        objects[o.id] = o
341
143
    graph = []
342
 
    checked = set()
343
 
    heads = list(set(heads))
344
 
    parent_invs_cache = LRUInventoryCache(repo)
 
144
    root_trees = {}
 
145
    revisions = {}
345
146
    # Find and convert commit objects
346
 
    while heads:
347
 
        if pb is not None:
348
 
            pb.update("finding revisions to fetch", len(graph), None)
349
 
        head = heads.pop()
350
 
        assert isinstance(head, str)
351
 
        try:
352
 
            o = lookup_object(head)
353
 
        except KeyError:
354
 
            continue
 
147
    for o in objects.itervalues():
355
148
        if isinstance(o, Commit):
356
149
            rev = mapping.import_commit(o)
357
 
            if repo.has_revision(rev.revision_id):
358
 
                continue
359
 
            squash_revision(repo, rev)
360
 
            graph.append((o.id, o.parents))
361
 
            heads.extend([p for p in o.parents if p not in checked])
362
 
        elif isinstance(o, Tag):
363
 
            if o.object[1] not in checked:
364
 
                heads.append(o.object[1])
365
 
        else:
366
 
            trace.warning("Unable to import head object %r" % o)
367
 
        checked.add(o.id)
368
 
    del checked
 
150
            root_trees[rev.revision_id] = objects[o.tree]
 
151
            revisions[rev.revision_id] = rev
 
152
            graph.append((rev.revision_id, rev.parent_ids))
369
153
    # Order the revisions
370
154
    # Create the inventory objects
371
 
    batch_size = 1000
372
 
    revision_ids = topo_sort(graph)
373
 
    pack_hints = []
374
 
    if limit is not None:
375
 
        revision_ids = revision_ids[:limit]
376
 
    last_imported = None
377
 
    for offset in range(0, len(revision_ids), batch_size):
378
 
        target_git_object_retriever.start_write_group() 
379
 
        try:
380
 
            repo.start_write_group()
381
 
            try:
382
 
                for i, head in enumerate(
383
 
                    revision_ids[offset:offset+batch_size]):
384
 
                    if pb is not None:
385
 
                        pb.update("fetching revisions", offset+i,
386
 
                                  len(revision_ids))
387
 
                    import_git_commit(repo, mapping, head, lookup_object,
388
 
                                      target_git_object_retriever,
389
 
                                      parent_invs_cache)
390
 
                    last_imported = head
391
 
            except:
392
 
                repo.abort_write_group()
393
 
                raise
394
 
            else:
395
 
                hint = repo.commit_write_group()
396
 
                if hint is not None:
397
 
                    pack_hints.extend(hint)
398
 
        except:
399
 
            target_git_object_retriever.abort_write_group()
400
 
            raise
401
 
        else:
402
 
            target_git_object_retriever.commit_write_group()
403
 
    return pack_hints, last_imported
404
 
 
405
 
 
406
 
class InterGitRepository(InterRepository):
407
 
 
408
 
    _matching_repo_format = GitRepositoryFormat()
 
155
    for i, revid in enumerate(topo_sort(graph)):
 
156
        if pb is not None:
 
157
            pb.update("fetching revisions", i, len(graph))
 
158
        root_tree = root_trees[revid]
 
159
        rev = revisions[revid]
 
160
        # We have to do this here, since we have to walk the tree and 
 
161
        # we need to make sure to import the blobs / trees with the riht 
 
162
        # path; this may involve adding them more than once.
 
163
        inv = Inventory()
 
164
        inv.revision_id = rev.revision_id
 
165
        def lookup_object(sha):
 
166
            if sha in objects:
 
167
                return objects[sha]
 
168
            return reconstruct_git_object(repo, mapping, sha)
 
169
        parent_invs = [repo.get_inventory(r) for r in rev.parent_ids]
 
170
        import_git_tree(repo, mapping, "", root_tree, inv, parent_invs, 
 
171
            lookup_object)
 
172
        repo.add_revision(rev.revision_id, rev, inv)
 
173
 
 
174
 
 
175
def reconstruct_git_commit(repo, rev):
 
176
    raise NotImplementedError(self.reconstruct_git_commit)
 
177
 
 
178
 
 
179
def reconstruct_git_object(repo, mapping, sha):
 
180
    # Commit
 
181
    revid = mapping.revision_id_foreign_to_bzr(sha)
 
182
    try:
 
183
        rev = repo.get_revision(revid)
 
184
    except NoSuchRevision:
 
185
        pass
 
186
    else:
 
187
        return reconstruct_git_commit(rev)
 
188
 
 
189
    # TODO: Tree
 
190
    # TODO: Blob
 
191
    raise KeyError("No such object %s" % sha)
 
192
 
 
193
 
 
194
class InterGitNonGitRepository(InterRepository):
 
195
 
 
196
    _matching_repo_format = GitFormat()
409
197
 
410
198
    @staticmethod
411
199
    def _get_repo_format_to_test():
415
203
        """See InterRepository.copy_content."""
416
204
        self.fetch(revision_id, pb, find_ghosts=False)
417
205
 
418
 
    def fetch(self, revision_id=None, pb=None, find_ghosts=False,
419
 
        mapping=None, fetch_spec=None):
420
 
        self.fetch_refs(revision_id=revision_id, pb=pb,
421
 
            find_ghosts=find_ghosts, mapping=mapping, fetch_spec=fetch_spec)
422
 
 
423
 
 
424
 
class InterGitNonGitRepository(InterGitRepository):
425
 
    """Base InterRepository that copies revisions from a Git into a non-Git
426
 
    repository."""
427
 
 
428
 
    def fetch_refs(self, revision_id=None, pb=None, find_ghosts=False,
429
 
              mapping=None, fetch_spec=None):
 
206
    def fetch(self, revision_id=None, pb=None, find_ghosts=False, 
 
207
              mapping=None):
430
208
        if mapping is None:
431
209
            mapping = self.source.get_mapping()
432
 
        if revision_id is not None:
433
 
            interesting_heads = [revision_id]
434
 
        elif fetch_spec is not None:
435
 
            interesting_heads = fetch_spec.heads
436
 
        else:
437
 
            interesting_heads = None
438
 
        self._refs = {}
439
 
        def determine_wants(refs):
440
 
            self._refs = refs
441
 
            if interesting_heads is None:
442
 
                ret = [sha for (ref, sha) in refs.iteritems() if not ref.endswith("^{}")]
 
210
        def progress(text):
 
211
            pb.update("git: %s" % text.rstrip("\r\n"), 0, 0)
 
212
        def determine_wants(heads):
 
213
            if revision_id is None:
 
214
                ret = heads.values()
443
215
            else:
444
 
                ret = [mapping.revision_id_bzr_to_foreign(revid)[0] for revid in interesting_heads if revid not in (None, NULL_REVISION)]
 
216
                ret = [mapping.revision_id_bzr_to_foreign(revision_id)[0]]
445
217
            return [rev for rev in ret if not self.target.has_revision(mapping.revision_id_foreign_to_bzr(rev))]
446
 
        (pack_hint, _) = self.fetch_objects(determine_wants, mapping, pb)
447
 
        if pack_hint is not None and self.target._format.pack_compresses:
448
 
            self.target.pack(hint=pack_hint)
449
 
        if interesting_heads is not None:
450
 
            present_interesting_heads = self.target.has_revisions(interesting_heads)
451
 
            missing_interesting_heads = set(interesting_heads) - present_interesting_heads
452
 
            if missing_interesting_heads:
453
 
                raise AssertionError("Missing interesting heads: %r" % missing_interesting_heads)
454
 
        return self._refs
455
 
 
456
 
 
457
 
_GIT_PROGRESS_RE = re.compile(r"(.*?): +(\d+)% \((\d+)/(\d+)\)")
458
 
def report_git_progress(pb, text):
459
 
    text = text.rstrip("\r\n")
460
 
    g = _GIT_PROGRESS_RE.match(text)
461
 
    if g is not None:
462
 
        (text, pct, current, total) = g.groups()
463
 
        pb.update(text, int(current), int(total))
464
 
    else:
465
 
        pb.update(text, 0, 0)
466
 
 
467
 
 
468
 
class InterRemoteGitNonGitRepository(InterGitNonGitRepository):
469
 
    """InterRepository that copies revisions from a remote Git into a non-Git
470
 
    repository."""
471
 
 
472
 
    def get_target_heads(self):
473
 
        # FIXME: This should be more efficient
474
 
        all_revs = self.target.all_revision_ids()
475
 
        parent_map = self.target.get_parent_map(all_revs)
476
 
        all_parents = set()
477
 
        map(all_parents.update, parent_map.itervalues())
478
 
        return set(all_revs) - all_parents
479
 
 
480
 
    def fetch_objects(self, determine_wants, mapping, pb=None, limit=None):
481
 
        def progress(text):
482
 
            report_git_progress(pb, text)
483
 
        store = BazaarObjectStore(self.target, mapping)
484
 
        self.target.lock_write()
485
 
        try:
486
 
            heads = self.get_target_heads()
487
 
            graph_walker = store.get_graph_walker(
488
 
                    [store._lookup_revision_sha1(head) for head in heads])
489
 
            recorded_wants = []
490
 
 
491
 
            def record_determine_wants(heads):
492
 
                wants = determine_wants(heads)
493
 
                recorded_wants.extend(wants)
494
 
                return wants
495
 
 
496
 
            create_pb = None
497
 
            if pb is None:
498
 
                create_pb = pb = ui.ui_factory.nested_progress_bar()
499
 
            try:
500
 
                objects_iter = self.source.fetch_objects(
501
 
                            record_determine_wants, graph_walker,
502
 
                            store.get_raw, progress)
503
 
                return import_git_objects(self.target, mapping,
504
 
                    objects_iter, store, recorded_wants, pb, limit)
505
 
            finally:
506
 
                if create_pb:
507
 
                    create_pb.finished()
508
 
        finally:
509
 
            self.target.unlock()
510
 
 
511
 
    @staticmethod
512
 
    def is_compatible(source, target):
513
 
        """Be compatible with GitRepository."""
514
 
        # FIXME: Also check target uses VersionedFile
515
 
        return (isinstance(source, RemoteGitRepository) and
516
 
                target.supports_rich_root() and
517
 
                not isinstance(target, GitRepository))
518
 
 
519
 
 
520
 
class InterLocalGitNonGitRepository(InterGitNonGitRepository):
521
 
    """InterRepository that copies revisions from a local Git into a non-Git
522
 
    repository."""
523
 
 
524
 
    def fetch_objects(self, determine_wants, mapping, pb=None, limit=None):
525
 
        """Fetch objects.
526
 
        """
527
 
        wants = determine_wants(self.source._git.get_refs())
 
218
        graph_walker = BzrFetchGraphWalker(self.target, mapping)
528
219
        create_pb = None
529
220
        if pb is None:
530
221
            create_pb = pb = ui.ui_factory.nested_progress_bar()
531
 
        target_git_object_retriever = BazaarObjectStore(self.target, mapping)
532
222
        try:
533
223
            self.target.lock_write()
534
224
            try:
535
 
                return import_git_objects(self.target, mapping,
536
 
                    self.source._git.object_store,
537
 
                    target_git_object_retriever, wants, pb, limit)
 
225
                self.target.start_write_group()
 
226
                try:
 
227
                    (num_objects, objects_iter) = \
 
228
                            self.source.fetch_objects(determine_wants, 
 
229
                                graph_walker, progress)
 
230
                    import_git_objects(self.target, mapping, num_objects, 
 
231
                                       objects_iter, pb)
 
232
                finally:
 
233
                    self.target.commit_write_group()
538
234
            finally:
539
235
                self.target.unlock()
540
236
        finally:
545
241
    def is_compatible(source, target):
546
242
        """Be compatible with GitRepository."""
547
243
        # FIXME: Also check target uses VersionedFile
548
 
        return (isinstance(source, LocalGitRepository) and
 
244
        return (isinstance(source, GitRepository) and 
549
245
                target.supports_rich_root() and
550
246
                not isinstance(target, GitRepository))
551
247
 
552
248
 
553
 
class InterGitGitRepository(InterGitRepository):
554
 
    """InterRepository that copies between Git repositories."""
555
 
 
556
 
    def fetch_objects(self, determine_wants, mapping, pb=None):
557
 
        def progress(text):
558
 
            trace.note("git: %s", text)
559
 
        graphwalker = self.target._git.get_graph_walker()
560
 
        if (isinstance(self.source, LocalGitRepository) and
561
 
            isinstance(self.target, LocalGitRepository)):
562
 
            return self.source._git.fetch(self.target._git, determine_wants,
563
 
                progress)
564
 
        elif (isinstance(self.source, LocalGitRepository) and
565
 
              isinstance(self.target, RemoteGitRepository)):
566
 
            raise NotImplementedError
567
 
        elif (isinstance(self.source, RemoteGitRepository) and
568
 
              isinstance(self.target, LocalGitRepository)):
569
 
            f, commit = self.target._git.object_store.add_thin_pack()
570
 
            try:
571
 
                refs = self.source._git.fetch_pack(determine_wants,
572
 
                    graphwalker, f.write, progress)
573
 
                commit()
574
 
                return refs
575
 
            except:
576
 
                f.close()
577
 
                raise
578
 
        else:
579
 
            raise AssertionError
580
 
 
581
 
    def fetch_refs(self, revision_id=None, pb=None, find_ghosts=False,
582
 
              mapping=None, fetch_spec=None, branches=None):
 
249
class InterGitRepository(InterRepository):
 
250
 
 
251
    _matching_repo_format = GitFormat()
 
252
 
 
253
    @staticmethod
 
254
    def _get_repo_format_to_test():
 
255
        return None
 
256
 
 
257
    def copy_content(self, revision_id=None, pb=None):
 
258
        """See InterRepository.copy_content."""
 
259
        self.fetch(revision_id, pb, find_ghosts=False)
 
260
 
 
261
    def fetch(self, revision_id=None, pb=None, find_ghosts=False, 
 
262
              mapping=None):
583
263
        if mapping is None:
584
264
            mapping = self.source.get_mapping()
 
265
        def progress(text):
 
266
            info("git: %s", text)
585
267
        r = self.target._git
586
 
        if revision_id is not None:
 
268
        if revision_id is None:
 
269
            determine_wants = lambda x: [y for y in x.values() if not y in r.object_store]
 
270
        else:
587
271
            args = [mapping.revision_id_bzr_to_foreign(revision_id)[0]]
588
 
        elif fetch_spec is not None:
589
 
            args = [mapping.revision_id_bzr_to_foreign(revid)[0] for revid in fetch_spec.heads]
590
 
        if branches is not None:
591
 
            determine_wants = lambda x: [x[y] for y in branches if not x[y] in r.object_store]
592
 
        elif fetch_spec is None and revision_id is None:
593
 
            determine_wants = r.object_store.determine_wants_all
594
 
        else:
595
272
            determine_wants = lambda x: [y for y in args if not y in r.object_store]
596
 
        return self.fetch_objects(determine_wants, mapping)[0]
597
273
 
 
274
        graphwalker = SimpleFetchGraphWalker(r.heads().values(), r.get_parents)
 
275
        f, commit = r.object_store.add_pack()
 
276
        try:
 
277
            self.source._git.fetch_pack(path, determine_wants, graphwalker, f.write, progress)
 
278
            f.close()
 
279
            commit()
 
280
        except:
 
281
            f.close()
 
282
            raise
598
283
 
599
284
    @staticmethod
600
285
    def is_compatible(source, target):
601
286
        """Be compatible with GitRepository."""
602
 
        return (isinstance(source, GitRepository) and
 
287
        return (isinstance(source, GitRepository) and 
603
288
                isinstance(target, GitRepository))