/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

Support submodules during fetch.

Show diffs side-by-side

added added

removed removed

Lines of Context:
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
17
from dulwich.objects import (
22
18
    Commit,
23
19
    Tag,
26
22
from dulwich.object_store import (
27
23
    tree_lookup_path,
28
24
    )
 
25
import re
29
26
import stat
30
27
 
31
28
from bzrlib import (
36
33
    urlutils,
37
34
    )
38
35
from bzrlib.errors import (
39
 
    InvalidRevisionId,
 
36
    BzrError,
40
37
    NoSuchId,
41
 
    NoSuchRevision,
42
38
    )
43
39
from bzrlib.inventory import (
44
40
    Inventory,
45
41
    InventoryDirectory,
46
42
    InventoryFile,
47
43
    InventoryLink,
 
44
    TreeReference,
48
45
    )
49
46
from bzrlib.lru_cache import (
50
47
    LRUCache,
67
64
    inventory_to_tree_and_blobs,
68
65
    mode_is_executable,
69
66
    squash_revision,
70
 
    text_to_blob,
71
67
    warn_unusual_mode,
72
68
    )
73
69
from bzrlib.plugins.git.object_store import (
77
73
    RemoteGitRepository,
78
74
    )
79
75
from bzrlib.plugins.git.repository import (
80
 
    GitRepository, 
 
76
    GitRepository,
81
77
    GitRepositoryFormat,
82
78
    LocalGitRepository,
83
79
    )
84
80
 
85
81
 
86
 
def import_git_blob(texts, mapping, path, hexsha, base_inv, parent_id, 
 
82
def import_git_blob(texts, mapping, path, hexsha, base_inv, base_ie, parent_id,
87
83
    revision_id, parent_invs, shagitmap, lookup_object, executable, symlink):
88
84
    """Import a git blob object into a bzr repository.
89
85
 
101
97
    ie = cls(file_id, urlutils.basename(path).decode("utf-8"), parent_id)
102
98
    ie.executable = executable
103
99
    # See if this has changed at all
104
 
    try:
105
 
        base_ie = base_inv[file_id]
106
 
    except NoSuchId:
107
 
        base_ie = None
 
100
    if base_ie is None:
108
101
        base_sha = None
109
102
    else:
110
103
        try:
160
153
        shamap = [(hexsha, "blob", (ie.file_id, ie.revision))]
161
154
    else:
162
155
        shamap = []
163
 
    if file_id in base_inv:
 
156
    invdelta = []
 
157
    if base_ie is not None:
164
158
        old_path = base_inv.id2path(file_id)
 
159
        if base_ie.kind == "directory":
 
160
            invdelta.extend(remove_disappeared_children(old_path, base_ie.children, []))
165
161
    else:
166
162
        old_path = None
167
 
    invdelta = [(old_path, path, file_id, ie)]
168
 
    invdelta.extend(remove_disappeared_children(base_inv, base_ie, []))
 
163
    invdelta.append((old_path, path, file_id, ie))
169
164
    return (invdelta, shamap)
170
165
 
171
166
 
172
 
def import_git_submodule(texts, mapping, path, hexsha, base_inv, parent_id, 
173
 
    revision_id, parent_invs, shagitmap, lookup_object):
174
 
    raise NotImplementedError(import_git_submodule)
175
 
 
176
 
 
177
 
def remove_disappeared_children(base_inv, base_ie, existing_children):
178
 
    if base_ie is None or base_ie.kind != 'directory':
179
 
        return []
 
167
class SubmodulesRequireSubtrees(BzrError):
 
168
    _fmt = """The repository you are fetching from contains submodules. Please run 'bzr upgrade --development-subtree'."""
 
169
    internal = False
 
170
 
 
171
 
 
172
def import_git_submodule(texts, mapping, path, hexsha, base_inv, base_ie,
 
173
    parent_id, revision_id, parent_invs, shagitmap, lookup_object):
 
174
    file_id = mapping.generate_file_id(path)
 
175
    ie = TreeReference(file_id, urlutils.basename(path.decode("utf-8")),
 
176
        parent_id)
 
177
    ie.revision = revision_id
 
178
    if base_ie is None:
 
179
        oldpath = None
 
180
    else:
 
181
        oldpath = path
 
182
        if base_ie.kind == ie.kind and base_ie.reference_revision == ie.reference_revision:
 
183
            ie.revision = base_ie.revision
 
184
    ie.reference_revision = mapping.revision_id_foreign_to_bzr(hexsha)
 
185
    texts.insert_record_stream([FulltextContentFactory((file_id, ie.revision), (), None, "")])
 
186
    invdelta = [(oldpath, path, file_id, ie)]
 
187
    return invdelta, {}, {}
 
188
 
 
189
 
 
190
def remove_disappeared_children(path, base_children, existing_children):
180
191
    ret = []
181
 
    deletable = [v for k,v in base_ie.children.iteritems() if k not in existing_children]
 
192
    deletable = [(osutils.pathjoin(path, k), v) for k,v in base_children.iteritems() if k not in existing_children]
182
193
    while deletable:
183
 
        ie = deletable.pop()
184
 
        ret.append((base_inv.id2path(ie.file_id), None, ie.file_id, None))
 
194
        (path, ie) = deletable.pop()
 
195
        ret.append((path, None, ie.file_id, None))
185
196
        if ie.kind == "directory":
186
 
            deletable.extend(ie.children.values())
 
197
            for name, child_ie in ie.children.iteritems():
 
198
                deletable.append((osutils.pathjoin(path, name), child_ie))
187
199
    return ret
188
200
 
189
201
 
190
 
def import_git_tree(texts, mapping, path, hexsha, base_inv, parent_id, 
 
202
def import_git_tree(texts, mapping, path, hexsha, base_inv, base_ie, parent_id,
191
203
    revision_id, parent_invs, shagitmap, lookup_object):
192
204
    """Import a git tree object into a bzr repository.
193
205
 
200
212
    invdelta = []
201
213
    file_id = mapping.generate_file_id(path)
202
214
    # We just have to hope this is indeed utf-8:
203
 
    ie = InventoryDirectory(file_id, urlutils.basename(path.decode("utf-8")), 
 
215
    ie = InventoryDirectory(file_id, urlutils.basename(path.decode("utf-8")),
204
216
        parent_id)
205
 
    try:
206
 
        base_ie = base_inv[file_id]
207
 
    except NoSuchId:
 
217
    if base_ie is None:
208
218
        # Newly appeared here
209
 
        base_ie = None
210
219
        ie.revision = revision_id
211
 
        texts.add_lines((file_id, ie.revision), (), [])
 
220
        texts.insert_record_stream([FulltextContentFactory((file_id, ie.revision), (), None, "")])
212
221
        invdelta.append((None, path, file_id, ie))
213
222
    else:
214
223
        # See if this has changed at all
222
231
                return [], {}, []
223
232
        if base_ie.kind != "directory":
224
233
            ie.revision = revision_id
225
 
            texts.add_lines((ie.file_id, ie.revision), (), [])
 
234
            texts.insert_record_stream([FulltextContentFactory((ie.file_id, ie.revision), (), None, "")])
226
235
            invdelta.append((base_inv.id2path(ie.file_id), path, ie.file_id, ie))
 
236
    if base_ie is not None and base_ie.kind == "directory":
 
237
        base_children = base_ie.children
 
238
    else:
 
239
        base_children = {}
227
240
    # Remember for next time
228
241
    existing_children = set()
229
242
    child_modes = {}
235
248
        child_path = osutils.pathjoin(path, name)
236
249
        if stat.S_ISDIR(mode):
237
250
            subinvdelta, grandchildmodes, subshamap = import_git_tree(
238
 
                    texts, mapping, child_path, child_hexsha, base_inv, 
239
 
                    file_id, revision_id, parent_invs, shagitmap, lookup_object)
 
251
                    texts, mapping, child_path, child_hexsha, base_inv,
 
252
                    base_children.get(basename), file_id, revision_id, parent_invs, shagitmap,
 
253
                    lookup_object)
240
254
            invdelta.extend(subinvdelta)
241
255
            child_modes.update(grandchildmodes)
242
256
            shamap.extend(subshamap)
243
257
        elif S_ISGITLINK(mode): # submodule
244
258
            subinvdelta, grandchildmodes, subshamap = import_git_submodule(
245
 
                    texts, mapping, child_path, child_hexsha, base_inv,
 
259
                    texts, mapping, child_path, child_hexsha, base_inv, base_children.get(basename),
246
260
                    file_id, revision_id, parent_invs, shagitmap, lookup_object)
247
261
            invdelta.extend(subinvdelta)
248
262
            child_modes.update(grandchildmodes)
249
263
            shamap.extend(subshamap)
250
264
        else:
251
 
            subinvdelta, subshamap = import_git_blob(texts, mapping, 
252
 
                    child_path, child_hexsha, base_inv, file_id, revision_id, 
253
 
                    parent_invs, shagitmap, lookup_object, 
 
265
            subinvdelta, subshamap = import_git_blob(texts, mapping,
 
266
                    child_path, child_hexsha, base_inv, base_children.get(basename), file_id,
 
267
                    revision_id, parent_invs, shagitmap, lookup_object,
254
268
                    mode_is_executable(mode), stat.S_ISLNK(mode))
255
269
            invdelta.extend(subinvdelta)
256
270
            shamap.extend(subshamap)
258
272
                        stat.S_IFLNK, DEFAULT_FILE_MODE|0111):
259
273
            child_modes[child_path] = mode
260
274
    # Remove any children that have disappeared
261
 
    invdelta.extend(remove_disappeared_children(base_inv, base_ie, existing_children))
 
275
    if base_ie is not None and base_ie.kind == "directory":
 
276
        invdelta.extend(remove_disappeared_children(base_inv.id2path(file_id),
 
277
            base_children, existing_children))
262
278
    shamap.append((hexsha, "tree", (file_id, revision_id)))
263
279
    return invdelta, child_modes, shamap
264
280
 
265
281
 
266
 
def import_git_objects(repo, mapping, object_iter, target_git_object_retriever, 
 
282
def import_git_objects(repo, mapping, object_iter, target_git_object_retriever,
267
283
        heads, pb=None):
268
284
    """Import a set of git objects into a bzr repository.
269
285
 
292
308
        try:
293
309
            o = lookup_object(head)
294
310
        except KeyError:
 
311
            trace.mutter('missing head %s', head)
295
312
            continue
296
313
        if isinstance(o, Commit):
297
314
            rev = mapping.import_commit(o)
301
318
            root_trees[rev.revision_id] = o.tree
302
319
            revisions[rev.revision_id] = rev
303
320
            graph.append((rev.revision_id, rev.parent_ids))
304
 
            target_git_object_retriever._idmap.add_entry(o.id, "commit", 
 
321
            target_git_object_retriever._idmap.add_entry(o.id, "commit",
305
322
                    (rev.revision_id, o.tree))
306
323
            heads.extend([p for p in o.parents if p not in checked])
307
324
        elif isinstance(o, Tag):
315
332
        if pb is not None:
316
333
            pb.update("fetching revisions", i, len(graph))
317
334
        rev = revisions[revid]
318
 
        # We have to do this here, since we have to walk the tree and 
319
 
        # we need to make sure to import the blobs / trees with the right 
 
335
        # We have to do this here, since we have to walk the tree and
 
336
        # we need to make sure to import the blobs / trees with the right
320
337
        # path; this may involve adding them more than once.
321
338
        parent_invs = []
322
339
        for parent_id in rev.parent_ids:
328
345
                parent_invs_cache[parent_id] = parent_inv
329
346
        if parent_invs == []:
330
347
            base_inv = Inventory(root_id=None)
 
348
            base_ie = None
331
349
        else:
332
350
            base_inv = parent_invs[0]
333
 
        inv_delta, unusual_modes, shamap = import_git_tree(repo.texts, 
334
 
                mapping, "", root_trees[revid], base_inv, None, revid, 
 
351
            base_ie = base_inv.root
 
352
        inv_delta, unusual_modes, shamap = import_git_tree(repo.texts,
 
353
                mapping, "", root_trees[revid], base_inv, base_ie, None, revid,
335
354
                parent_invs, target_git_object_retriever._idmap, lookup_object)
336
355
        target_git_object_retriever._idmap.add_entries(shamap)
337
356
        if unusual_modes != {}:
342
361
            basis_id = rev.parent_ids[0]
343
362
        except IndexError:
344
363
            basis_id = NULL_REVISION
 
364
            base_inv = None
345
365
        rev.inventory_sha1, inv = repo.add_inventory_by_delta(basis_id,
346
 
                  inv_delta, rev.revision_id, rev.parent_ids)
 
366
                  inv_delta, rev.revision_id, rev.parent_ids,
 
367
                  base_inv)
347
368
        parent_invs_cache[rev.revision_id] = inv
348
369
        repo.add_revision(rev.revision_id, rev)
349
370
        if "verify" in debug.debug_flags:
379
400
 
380
401
 
381
402
class InterGitNonGitRepository(InterGitRepository):
382
 
    """Base InterRepository that copies revisions from a Git into a non-Git 
 
403
    """Base InterRepository that copies revisions from a Git into a non-Git
383
404
    repository."""
384
405
 
385
 
    def fetch_refs(self, revision_id=None, pb=None, find_ghosts=False, 
 
406
    def fetch_refs(self, revision_id=None, pb=None, find_ghosts=False,
386
407
              mapping=None, fetch_spec=None):
387
408
        if mapping is None:
388
409
            mapping = self.source.get_mapping()
400
421
            else:
401
422
                ret = [mapping.revision_id_bzr_to_foreign(revid)[0] for revid in interesting_heads if revid not in (None, NULL_REVISION)]
402
423
            return [rev for rev in ret if not self.target.has_revision(mapping.revision_id_foreign_to_bzr(rev))]
403
 
        self.fetch_objects(determine_wants, mapping, pb)
 
424
        pack_hint = self.fetch_objects(determine_wants, mapping, pb)
 
425
        if pack_hint is not None and self.target._format.pack_compresses:
 
426
            self.target.pack(hint=pack_hint)
 
427
        if interesting_heads is not None:
 
428
            present_interesting_heads = self.target.has_revisions(interesting_heads)
 
429
            missing_interesting_heads = set(interesting_heads) - present_interesting_heads
 
430
            if missing_interesting_heads:
 
431
                raise AssertionError("Missing interesting heads: %r" % missing_interesting_heads)
404
432
        return self._refs
405
433
 
406
434
 
 
435
_GIT_PROGRESS_RE = re.compile(r"(.*?): +(\d+)% \((\d+)/(\d+)\)")
 
436
def report_git_progress(pb, text):
 
437
    text = text.rstrip("\r\n")
 
438
    g = _GIT_PROGRESS_RE.match(text)
 
439
    if g is not None:
 
440
        (text, pct, current, total) = g.groups()
 
441
        pb.update(text, int(current), int(total))
 
442
    else:
 
443
        pb.update(text, 0, 0)
 
444
 
 
445
 
407
446
class InterRemoteGitNonGitRepository(InterGitNonGitRepository):
408
 
    """InterRepository that copies revisions from a remote Git into a non-Git 
 
447
    """InterRepository that copies revisions from a remote Git into a non-Git
409
448
    repository."""
410
449
 
 
450
    def get_target_heads(self):
 
451
        # FIXME: This should be more efficient
 
452
        all_revs = self.target.all_revision_ids()
 
453
        parent_map = self.target.get_parent_map(all_revs)
 
454
        all_parents = set()
 
455
        map(all_parents.update, parent_map.itervalues())
 
456
        return set(all_revs) - all_parents
 
457
 
411
458
    def fetch_objects(self, determine_wants, mapping, pb=None):
412
459
        def progress(text):
413
 
            pb.update("git: %s" % text.rstrip("\r\n"), 0, 0)
 
460
            report_git_progress(pb, text)
414
461
        store = BazaarObjectStore(self.target, mapping)
415
462
        self.target.lock_write()
416
463
        try:
417
 
            heads = self.target.get_graph().heads(self.target.all_revision_ids())
 
464
            heads = self.get_target_heads()
418
465
            graph_walker = store.get_graph_walker(
419
466
                    [store._lookup_revision_sha1(head) for head in heads])
420
467
            recorded_wants = []
423
470
                wants = determine_wants(heads)
424
471
                recorded_wants.extend(wants)
425
472
                return wants
426
 
        
 
473
 
427
474
            create_pb = None
428
475
            if pb is None:
429
476
                create_pb = pb = ui.ui_factory.nested_progress_bar()
431
478
                self.target.start_write_group()
432
479
                try:
433
480
                    objects_iter = self.source.fetch_objects(
434
 
                                record_determine_wants, graph_walker, 
 
481
                                record_determine_wants, graph_walker,
435
482
                                store.get_raw, progress)
436
 
                    import_git_objects(self.target, mapping, objects_iter, 
 
483
                    import_git_objects(self.target, mapping, objects_iter,
437
484
                            store, recorded_wants, pb)
438
485
                finally:
439
 
                    self.target.commit_write_group()
 
486
                    pack_hint = self.target.commit_write_group()
 
487
                return pack_hint
440
488
            finally:
441
489
                if create_pb:
442
490
                    create_pb.finished()
447
495
    def is_compatible(source, target):
448
496
        """Be compatible with GitRepository."""
449
497
        # FIXME: Also check target uses VersionedFile
450
 
        return (isinstance(source, RemoteGitRepository) and 
 
498
        return (isinstance(source, RemoteGitRepository) and
451
499
                target.supports_rich_root() and
452
500
                not isinstance(target, GitRepository))
453
501
 
454
502
 
455
503
class InterLocalGitNonGitRepository(InterGitNonGitRepository):
456
 
    """InterRepository that copies revisions from a local Git into a non-Git 
 
504
    """InterRepository that copies revisions from a local Git into a non-Git
457
505
    repository."""
458
506
 
459
507
    def fetch_objects(self, determine_wants, mapping, pb=None):
467
515
            try:
468
516
                self.target.start_write_group()
469
517
                try:
470
 
                    import_git_objects(self.target, mapping, 
471
 
                            self.source._git.object_store, 
 
518
                    import_git_objects(self.target, mapping,
 
519
                            self.source._git.object_store,
472
520
                            target_git_object_retriever, wants, pb)
473
521
                finally:
474
 
                    self.target.commit_write_group()
 
522
                    pack_hint = self.target.commit_write_group()
 
523
                return pack_hint
475
524
            finally:
476
525
                self.target.unlock()
477
526
        finally:
482
531
    def is_compatible(source, target):
483
532
        """Be compatible with GitRepository."""
484
533
        # FIXME: Also check target uses VersionedFile
485
 
        return (isinstance(source, LocalGitRepository) and 
 
534
        return (isinstance(source, LocalGitRepository) and
486
535
                target.supports_rich_root() and
487
536
                not isinstance(target, GitRepository))
488
537
 
490
539
class InterGitGitRepository(InterGitRepository):
491
540
    """InterRepository that copies between Git repositories."""
492
541
 
493
 
    def fetch_refs(self, revision_id=None, pb=None, find_ghosts=False, 
 
542
    def fetch_objects(self, determine_wants, mapping, pb=None):
 
543
        def progress(text):
 
544
            trace.note("git: %s", text)
 
545
        graphwalker = self.target._git.get_graph_walker()
 
546
        if isinstance(self.source, LocalGitRepository) and isinstance(self.target, LocalGitRepository):
 
547
            return self.source._git.fetch(self.target._git, determine_wants,
 
548
                progress)
 
549
        elif isinstance(self.source, LocalGitRepository) and isinstance(self.target, RemoteGitRepository):
 
550
            raise NotImplementedError
 
551
        elif isinstance(self.source, RemoteGitRepository) and isinstance(self.target, LocalGitRepository):
 
552
            f, commit = self.target._git.object_store.add_thin_pack()
 
553
            try:
 
554
                refs = self.source._git.fetch_pack(determine_wants, graphwalker,
 
555
                                                   f.write, progress)
 
556
                commit()
 
557
                return refs
 
558
            except:
 
559
                f.close()
 
560
                raise
 
561
        else:
 
562
            raise AssertionError
 
563
 
 
564
    def fetch_refs(self, revision_id=None, pb=None, find_ghosts=False,
494
565
              mapping=None, fetch_spec=None, branches=None):
495
566
        if mapping is None:
496
567
            mapping = self.source.get_mapping()
497
 
        def progress(text):
498
 
            trace.info("git: %s", text)
499
568
        r = self.target._git
500
569
        if revision_id is not None:
501
570
            args = [mapping.revision_id_bzr_to_foreign(revision_id)[0]]
507
576
            determine_wants = r.object_store.determine_wants_all
508
577
        else:
509
578
            determine_wants = lambda x: [y for y in args if not y in r.object_store]
 
579
        return self.fetch_objects(determine_wants, mapping)
510
580
 
511
 
        graphwalker = r.get_graph_walker()
512
 
        f, commit = r.object_store.add_thin_pack()
513
 
        try:
514
 
            refs = self.source.fetch_pack(determine_wants, graphwalker,
515
 
                                          f.write, progress)
516
 
            commit()
517
 
            return refs
518
 
        except:
519
 
            f.close()
520
 
            raise
521
581
 
522
582
    @staticmethod
523
583
    def is_compatible(source, target):
524
584
        """Be compatible with GitRepository."""
525
 
        return (isinstance(source, GitRepository) and 
 
585
        return (isinstance(source, GitRepository) and
526
586
                isinstance(target, GitRepository))