/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 object_store.py

Add initial work on git-apply.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2009 Canonical Ltd
 
1
# Copyright (C) 2009 Jelmer Vernooij <jelmer@samba.org>
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
16
16
 
17
17
"""Map from Git sha's to Bazaar objects."""
18
18
 
19
 
import bzrlib
20
 
 
21
 
from bzrlib import ui
22
 
 
23
 
from bzrlib.errors import NoSuchRevision
24
 
 
25
 
from bzrlib.plugins.git.mapping import (
26
 
    inventory_to_tree_and_blobs,
27
 
    revision_to_commit,
28
 
    )
29
 
from bzrlib.plugins.git.shamap import GitShaMap
30
 
 
31
19
from dulwich.objects import (
32
20
    Blob,
33
 
    )
34
 
 
35
 
 
36
 
class GitObjectConverter(object):
 
21
    Tree,
 
22
    sha_to_hex,
 
23
    )
 
24
from dulwich.object_store import (
 
25
    BaseObjectStore,
 
26
    )
 
27
 
 
28
from bzrlib import (
 
29
    errors,
 
30
    lru_cache,
 
31
    trace,
 
32
    ui,
 
33
    urlutils,
 
34
    )
 
35
from bzrlib.revision import (
 
36
    NULL_REVISION,
 
37
    )
 
38
 
 
39
from bzrlib.plugins.git.mapping import (
 
40
    default_mapping,
 
41
    directory_to_tree,
 
42
    extract_unusual_modes,
 
43
    mapping_registry,
 
44
    symlink_to_blob,
 
45
    )
 
46
from bzrlib.plugins.git.shamap import (
 
47
    from_repository as cache_from_repository,
 
48
    )
 
49
 
 
50
import posixpath
 
51
 
 
52
 
 
53
def get_object_store(repo, mapping=None):
 
54
    git = getattr(repo, "_git", None)
 
55
    if git is not None:
 
56
        return git.object_store
 
57
    return BazaarObjectStore(repo, mapping)
 
58
 
 
59
 
 
60
MAX_TREE_CACHE_SIZE = 50 * 1024 * 1024
 
61
 
 
62
 
 
63
class LRUTreeCache(object):
 
64
 
 
65
    def __init__(self, repository):
 
66
        def approx_tree_size(tree):
 
67
            # Very rough estimate, 1k per inventory entry
 
68
            return len(tree.inventory) * 1024
 
69
        self.repository = repository
 
70
        self._cache = lru_cache.LRUSizeCache(max_size=MAX_TREE_CACHE_SIZE,
 
71
            after_cleanup_size=None, compute_size=approx_tree_size)
 
72
 
 
73
    def revision_tree(self, revid):            
 
74
        try:
 
75
            return self._cache[revid] 
 
76
        except KeyError:
 
77
            tree = self.repository.revision_tree(revid)
 
78
            self.add(tree)
 
79
            return tree
 
80
 
 
81
    def iter_revision_trees(self, revids):
 
82
        trees = dict([(k, self._cache.get(k)) for k in revids]) 
 
83
        for tree in self.repository.revision_trees(
 
84
                [r for r, v in trees.iteritems() if v is None]):
 
85
            trees[tree.get_revision_id()] = tree
 
86
            self.add(tree)
 
87
        return (trees[r] for r in revids)
 
88
 
 
89
    def revision_trees(self, revids):
 
90
        return list(self.iter_revision_trees(revids))
 
91
 
 
92
    def add(self, tree):
 
93
        self._cache.add(tree.get_revision_id(), tree)
 
94
 
 
95
 
 
96
def _check_expected_sha(expected_sha, object):
 
97
    """Check whether an object matches an expected SHA.
 
98
 
 
99
    :param expected_sha: None or expected SHA as either binary or as hex digest
 
100
    :param object: Object to verify
 
101
    """
 
102
    if expected_sha is None:
 
103
        return
 
104
    if len(expected_sha) == 40:
 
105
        if expected_sha != object.sha().hexdigest():
 
106
            raise AssertionError("Invalid sha for %r: %s" % (object,
 
107
                expected_sha))
 
108
    elif len(expected_sha) == 20:
 
109
        if expected_sha != object.sha().digest():
 
110
            raise AssertionError("Invalid sha for %r: %s" % (object,
 
111
                sha_to_hex(expected_sha)))
 
112
    else:
 
113
        raise AssertionError("Unknown length %d for %r" % (len(expected_sha),
 
114
            expected_sha))
 
115
 
 
116
 
 
117
def _tree_to_objects(tree, parent_trees, idmap, unusual_modes):
 
118
    """Iterate over the objects that were introduced in a revision.
 
119
 
 
120
    :param idmap: id map
 
121
    :param unusual_modes: Unusual file modes
 
122
    :return: Yields (path, object, ie) entries
 
123
    """
 
124
    new_trees = {}
 
125
    new_blobs = []
 
126
    shamap = {}
 
127
    try:
 
128
        base_tree = parent_trees[0]
 
129
        other_parent_trees = parent_trees[1:]
 
130
    except IndexError:
 
131
        base_tree = tree._repository.revision_tree(NULL_REVISION)
 
132
        other_parent_trees = []
 
133
    def find_unchanged_parent_ie(ie, parent_trees):
 
134
        assert ie.kind in ("symlink", "file")
 
135
        for ptree in parent_trees:
 
136
            try:
 
137
                pie = ptree.inventory[ie.file_id]
 
138
            except errors.NoSuchId:
 
139
                pass
 
140
            else:
 
141
                if (pie.text_sha1 == ie.text_sha1 and 
 
142
                    pie.kind == ie.kind and
 
143
                    pie.symlink_target == ie.symlink_target):
 
144
                    return pie
 
145
        raise KeyError
 
146
    for (file_id, path, changed_content, versioned, parent, name, kind,
 
147
         executable) in tree.iter_changes(base_tree):
 
148
        if kind[1] == "file":
 
149
            ie = tree.inventory[file_id]
 
150
            if changed_content:
 
151
                
 
152
                try:
 
153
                    pie = find_unchanged_parent_ie(ie, other_parent_trees)
 
154
                except KeyError:
 
155
                    pass
 
156
                else:
 
157
                    shamap[ie.file_id] = idmap.lookup_blob_id(
 
158
                        pie.file_id, pie.revision)
 
159
            if not file_id in shamap:
 
160
                new_blobs.append((path[1], ie))
 
161
            new_trees[posixpath.dirname(path[1])] = parent[1]
 
162
        elif kind[1] == "symlink":
 
163
            ie = tree.inventory[file_id]
 
164
            if changed_content:
 
165
                blob = symlink_to_blob(ie)
 
166
                shamap[file_id] = blob.id
 
167
                try:
 
168
                    find_unchanged_parent_ie(ie, other_parent_trees)
 
169
                except KeyError:
 
170
                    yield path[1], blob, ie
 
171
            new_trees[posixpath.dirname(path[1])] = parent[1]
 
172
        elif kind[1] not in (None, "directory"):
 
173
            raise AssertionError(kind[1])
 
174
        if path[0] is not None:
 
175
            new_trees[posixpath.dirname(path[0])] = parent[0]
 
176
    
 
177
    for (path, ie), chunks in tree.iter_files_bytes(
 
178
        [(ie.file_id, (path, ie)) for (path, ie) in new_blobs]):
 
179
        obj = Blob()
 
180
        obj.chunked = chunks
 
181
        yield path, obj, ie
 
182
        shamap[ie.file_id] = obj.id
 
183
 
 
184
    for path in unusual_modes:
 
185
        parent_path = posixpath.dirname(path)
 
186
        new_trees[parent_path] = tree.path2id(parent_path)
 
187
    
 
188
    trees = {}
 
189
    while new_trees:
 
190
        items = new_trees.items()
 
191
        new_trees = {}
 
192
        for path, file_id in items:
 
193
            try:
 
194
                parent_id = tree.inventory[file_id].parent_id
 
195
            except errors.NoSuchId:
 
196
                # Directory was removed recursively perhaps ?
 
197
                continue
 
198
            if parent_id is not None:
 
199
                parent_path = urlutils.dirname(path)
 
200
                new_trees[parent_path] = parent_id
 
201
            trees[path] = file_id
 
202
 
 
203
    def ie_to_hexsha(ie):
 
204
        try:
 
205
            return shamap[ie.file_id]
 
206
        except KeyError:
 
207
            # FIXME: Should be the same as in parent
 
208
            if ie.kind in ("file", "symlink"):
 
209
                try:
 
210
                    return idmap.lookup_blob_id(ie.file_id, ie.revision)
 
211
                except KeyError:
 
212
                    # no-change merge ?
 
213
                    blob = Blob()
 
214
                    blob.data = tree.get_file_text(ie.file_id)
 
215
                    return blob.id
 
216
            elif ie.kind == "directory":
 
217
                # Not all cache backends store the tree information, 
 
218
                # calculate again from scratch
 
219
                ret = directory_to_tree(ie, ie_to_hexsha, unusual_modes)
 
220
                if ret is None:
 
221
                    return ret
 
222
                return ret.id
 
223
            else:
 
224
                raise AssertionError
 
225
 
 
226
    for path in sorted(trees.keys(), reverse=True):
 
227
        ie = tree.inventory[trees[path]]
 
228
        assert ie.kind == "directory"
 
229
        obj = directory_to_tree(ie, ie_to_hexsha, unusual_modes)
 
230
        if obj is not None:
 
231
            yield path, obj, ie
 
232
            shamap[ie.file_id] = obj.id
 
233
 
 
234
 
 
235
class BazaarObjectStore(BaseObjectStore):
 
236
    """A Git-style object store backed onto a Bazaar repository."""
37
237
 
38
238
    def __init__(self, repository, mapping=None):
39
239
        self.repository = repository
40
240
        if mapping is None:
41
 
            self.mapping = self.repository.get_mapping()
 
241
            self.mapping = default_mapping
42
242
        else:
43
243
            self.mapping = mapping
44
 
        self._idmap = GitShaMap(self.repository._transport)
 
244
        self._cache = cache_from_repository(repository)
 
245
        self._content_cache_types = ("tree")
 
246
        self.start_write_group = self._cache.idmap.start_write_group
 
247
        self.abort_write_group = self._cache.idmap.abort_write_group
 
248
        self.commit_write_group = self._cache.idmap.commit_write_group
 
249
        self.tree_cache = LRUTreeCache(self.repository)
45
250
 
46
 
    def _update_sha_map(self):
47
 
        all_revids = self.repository.all_revision_ids()
 
251
    def _update_sha_map(self, stop_revision=None):
48
252
        graph = self.repository.get_graph()
49
 
        present_revids = set(self._idmap.revids())
50
 
        pb = ui.ui_factory.nested_progress_bar()
51
 
        try:
52
 
            for i, revid in enumerate(graph.iter_topo_order(all_revids)):
53
 
                if revid in present_revids:
54
 
                    continue
55
 
                pb.update("updating git map", i, len(all_revids))
56
 
                self._update_sha_map_revision(revid)
57
 
        finally:
58
 
            pb.finished()
 
253
        if stop_revision is None:
 
254
            heads = graph.heads(self.repository.all_revision_ids())
 
255
        else:
 
256
            heads = set([stop_revision])
 
257
        missing_revids = self._cache.idmap.missing_revisions(heads)
 
258
        while heads:
 
259
            parents = graph.get_parent_map(heads)
 
260
            todo = set()
 
261
            for p in parents.values():
 
262
                todo.update([x for x in p if x not in missing_revids])
 
263
            heads = self._cache.idmap.missing_revisions(todo)
 
264
            missing_revids.update(heads)
 
265
        if NULL_REVISION in missing_revids:
 
266
            missing_revids.remove(NULL_REVISION)
 
267
        missing_revids = self.repository.has_revisions(missing_revids)
 
268
        if not missing_revids:
 
269
            return
 
270
        self.start_write_group()
 
271
        try:
 
272
            pb = ui.ui_factory.nested_progress_bar()
 
273
            try:
 
274
                for i, revid in enumerate(graph.iter_topo_order(missing_revids)):
 
275
                    trace.mutter('processing %r', revid)
 
276
                    pb.update("updating git map", i, len(missing_revids))
 
277
                    self._update_sha_map_revision(revid)
 
278
            finally:
 
279
                pb.finished()
 
280
        except:
 
281
            self.abort_write_group()
 
282
            raise
 
283
        else:
 
284
            self.commit_write_group()
 
285
 
 
286
    def __iter__(self):
 
287
        self._update_sha_map()
 
288
        return iter(self._cache.idmap.sha1s())
 
289
 
 
290
    def _reconstruct_commit(self, rev, tree_sha):
 
291
        def parent_lookup(revid):
 
292
            try:
 
293
                return self._lookup_revision_sha1(revid)
 
294
            except errors.NoSuchRevision:
 
295
                trace.warning("Ignoring ghost parent %s", revid)
 
296
                return None
 
297
        return self.mapping.export_commit(rev, tree_sha, parent_lookup)
 
298
 
 
299
    def _revision_to_objects(self, rev, tree):
 
300
        unusual_modes = extract_unusual_modes(rev)
 
301
        present_parents = self.repository.has_revisions(rev.parent_ids)
 
302
        parent_trees = self.tree_cache.revision_trees(
 
303
            [p for p in rev.parent_ids if p in present_parents])
 
304
        tree_sha = None
 
305
        for path, obj, ie in _tree_to_objects(tree, parent_trees,
 
306
                self._cache.idmap, unusual_modes):
 
307
            yield path, obj, ie
 
308
            if path == "":
 
309
                tree_sha = obj.id
 
310
        if tree_sha is None:
 
311
            # Pointless commit - get the tree sha elsewhere
 
312
            if not rev.parent_ids:
 
313
                tree_sha = Tree().id
 
314
            else:
 
315
                base_sha1 = self._lookup_revision_sha1(rev.parent_ids[0])
 
316
                tree_sha = self[base_sha1].tree
 
317
        commit_obj = self._reconstruct_commit(rev, tree_sha)
 
318
        try:
 
319
            foreign_revid, mapping = mapping_registry.parse_revision_id(
 
320
                rev.revision_id)
 
321
        except errors.InvalidRevisionId:
 
322
            pass
 
323
        else:
 
324
            _check_expected_sha(foreign_revid, commit_obj)
 
325
        yield None, commit_obj, None
 
326
 
 
327
    def _get_updater(self, rev):
 
328
        return self._cache.get_updater(rev)
59
329
 
60
330
    def _update_sha_map_revision(self, revid):
61
 
        inv = self.repository.get_inventory(revid)
62
 
        objects = inventory_to_tree_and_blobs(self.repository, self.mapping, revid)
63
 
        for sha, o, path in objects:
64
 
            if path == "":
65
 
                tree_sha = sha
66
 
            ie = inv[inv.path2id(path)]
67
 
            if ie.kind in ("file", "symlink"):
68
 
                self._idmap.add_entry(sha, "blob", (ie.file_id, ie.revision))
69
 
            else:
70
 
                self._idmap.add_entry(sha, "tree", (ie.file_id, ie.revision))
71
 
        rev = self.repository.get_revision(revid)
72
 
        commit_obj = revision_to_commit(rev, tree_sha, self._idmap._parent_lookup)
73
 
        self._idmap.add_entry(commit_obj.sha().hexdigest(), "commit", (revid, tree_sha))
74
 
 
75
 
    def _get_blob(self, fileid, revision):
76
 
        text = self.repository.texts.get_record_stream([(fileid, revision)], "unordered", True).next().get_bytes_as("fulltext")
77
 
        blob = Blob()
78
 
        blob._text = text
79
 
        return blob
80
 
 
81
 
    def _get_tree(self, fileid, revid):
82
 
        raise NotImplementedError(self._get_tree)
83
 
 
84
 
    def _get_commit(self, revid, tree_sha):
85
 
        rev = self.repository.get_revision(revid)
86
 
        return revision_to_commit(rev, tree_sha, self._idmap._parent_lookup)
87
 
 
88
 
    def __getitem__(self, sha):
89
 
        # See if sha is in map
90
 
        try:
91
 
            (type, type_data) = self._idmap.lookup_git_sha(sha)
92
 
        except KeyError:
93
 
            # if not, see if there are any unconverted revisions and add them 
 
331
        rev = self.repository.get_revision(revid)
 
332
        tree = self.tree_cache.revision_tree(rev.revision_id)
 
333
        updater = self._get_updater(rev)
 
334
        for path, obj, ie in self._revision_to_objects(rev, tree):
 
335
            updater.add_object(obj, ie)
 
336
        commit_obj = updater.finish()
 
337
        return commit_obj.id
 
338
 
 
339
    def _reconstruct_blobs(self, keys):
 
340
        """Return a Git Blob object from a fileid and revision stored in bzr.
 
341
 
 
342
        :param fileid: File id of the text
 
343
        :param revision: Revision of the text
 
344
        """
 
345
        stream = self.repository.iter_files_bytes(
 
346
            ((key[0], key[1], key) for key in keys))
 
347
        for (fileid, revision, expected_sha), chunks in stream:
 
348
            blob = Blob()
 
349
            blob.chunked = chunks
 
350
            if blob.id != expected_sha and blob.data == "":
 
351
                # Perhaps it's a symlink ?
 
352
                tree = self.tree_cache.revision_tree(revision)
 
353
                entry = tree.inventory[fileid]
 
354
                if entry.kind == 'symlink':
 
355
                    blob = symlink_to_blob(entry)
 
356
            _check_expected_sha(expected_sha, blob)
 
357
            yield blob
 
358
 
 
359
    def _reconstruct_tree(self, fileid, revid, inv, unusual_modes,
 
360
        expected_sha=None):
 
361
        """Return a Git Tree object from a file id and a revision stored in bzr.
 
362
 
 
363
        :param fileid: fileid in the tree.
 
364
        :param revision: Revision of the tree.
 
365
        """
 
366
        def get_ie_sha1(entry):
 
367
            if entry.kind == "directory":
 
368
                try:
 
369
                    return self._cache.idmap.lookup_tree_id(entry.file_id,
 
370
                        revid)
 
371
                except (NotImplementedError, KeyError):
 
372
                    obj = self._reconstruct_tree(entry.file_id, revid, inv,
 
373
                        unusual_modes)
 
374
                    if obj is None:
 
375
                        return None
 
376
                    else:
 
377
                        return obj.id
 
378
            elif entry.kind in ("file", "symlink"):
 
379
                try:
 
380
                    return self._cache.idmap.lookup_blob_id(entry.file_id,
 
381
                        entry.revision)
 
382
                except KeyError:
 
383
                    # no-change merge?
 
384
                    return self._reconstruct_blobs(
 
385
                        [(entry.file_id, entry.revision, None)]).next().id
 
386
            else:
 
387
                raise AssertionError("unknown entry kind '%s'" % entry.kind)
 
388
        tree = directory_to_tree(inv[fileid], get_ie_sha1, unusual_modes)
 
389
        _check_expected_sha(expected_sha, tree)
 
390
        return tree
 
391
 
 
392
    def get_parents(self, sha):
 
393
        """Retrieve the parents of a Git commit by SHA1.
 
394
 
 
395
        :param sha: SHA1 of the commit
 
396
        :raises: KeyError, NotCommitError
 
397
        """
 
398
        return self[sha].parents
 
399
 
 
400
    def _lookup_revision_sha1(self, revid):
 
401
        """Return the SHA1 matching a Bazaar revision."""
 
402
        from dulwich.protocol import ZERO_SHA
 
403
        if revid == NULL_REVISION:
 
404
            return ZERO_SHA
 
405
        try:
 
406
            return self._cache.idmap.lookup_commit(revid)
 
407
        except KeyError:
 
408
            try:
 
409
                return mapping_registry.parse_revision_id(revid)[0]
 
410
            except errors.InvalidRevisionId:
 
411
                self._update_sha_map(revid)
 
412
                return self._cache.idmap.lookup_commit(revid)
 
413
 
 
414
    def get_raw(self, sha):
 
415
        """Get the raw representation of a Git object by SHA1.
 
416
 
 
417
        :param sha: SHA1 of the git object
 
418
        """
 
419
        obj = self[sha]
 
420
        return (obj.type, obj.as_raw_string())
 
421
 
 
422
    def __contains__(self, sha):
 
423
        # See if sha is in map
 
424
        try:
 
425
            (type, type_data) = self._lookup_git_sha(sha)
 
426
            if type == "commit":
 
427
                return self.repository.has_revision(type_data[0])
 
428
            elif type == "blob":
 
429
                return self.repository.texts.has_version(type_data)
 
430
            elif type == "tree":
 
431
                return self.repository.has_revision(type_data[1])
 
432
            else:
 
433
                raise AssertionError("Unknown object type '%s'" % type)
 
434
        except KeyError:
 
435
            return False
 
436
 
 
437
    def _lookup_git_sha(self, sha):
 
438
        # See if sha is in map
 
439
        try:
 
440
            return self._cache.idmap.lookup_git_sha(sha)
 
441
        except KeyError:
 
442
            # if not, see if there are any unconverted revisions and add them
94
443
            # to the map, search for sha in map again
95
444
            self._update_sha_map()
96
 
            (type, type_data) = self._idmap.lookup_git_sha(sha)
 
445
            return self._cache.idmap.lookup_git_sha(sha)
 
446
 
 
447
    def __getitem__(self, sha):
 
448
        if self._cache.content_cache is not None:
 
449
            try:
 
450
                return self._cache.content_cache[sha]
 
451
            except KeyError:
 
452
                pass
 
453
        (type, type_data) = self._lookup_git_sha(sha)
97
454
        # convert object to git object
98
455
        if type == "commit":
99
 
            return self._get_commit(*type_data)
 
456
            (revid, tree_sha) = type_data
 
457
            try:
 
458
                rev = self.repository.get_revision(revid)
 
459
            except errors.NoSuchRevision:
 
460
                trace.mutter('entry for %s %s in shamap: %r, but not found in '
 
461
                             'repository', type, sha, type_data)
 
462
                raise KeyError(sha)
 
463
            commit = self._reconstruct_commit(rev, tree_sha)
 
464
            _check_expected_sha(sha, commit)
 
465
            return commit
100
466
        elif type == "blob":
101
 
            return self._get_blob(*type_data)
 
467
            (fileid, revision) = type_data
 
468
            return self._reconstruct_blobs([(fileid, revision, sha)]).next()
102
469
        elif type == "tree":
103
 
            return self._get_tree(*type_data)
 
470
            (fileid, revid) = type_data
 
471
            try:
 
472
                tree = self.tree_cache.revision_tree(revid)
 
473
                rev = self.repository.get_revision(revid)
 
474
            except errors.NoSuchRevision:
 
475
                trace.mutter('entry for %s %s in shamap: %r, but not found in repository', type, sha, type_data)
 
476
                raise KeyError(sha)
 
477
            unusual_modes = extract_unusual_modes(rev)
 
478
            try:
 
479
                return self._reconstruct_tree(fileid, revid, tree.inventory,
 
480
                    unusual_modes, expected_sha=sha)
 
481
            except errors.NoSuchRevision:
 
482
                raise KeyError(sha)
104
483
        else:
105
484
            raise AssertionError("Unknown object type '%s'" % type)
 
485
 
 
486
    def generate_pack_contents(self, have, want, progress=None, get_tagged=None):
 
487
        """Iterate over the contents of a pack file.
 
488
 
 
489
        :param have: List of SHA1s of objects that should not be sent
 
490
        :param want: List of SHA1s of objects that should be sent
 
491
        """
 
492
        processed = set()
 
493
        for commit_sha in have:
 
494
            try:
 
495
                (type, (revid, tree_sha)) = self._lookup_git_sha(commit_sha)
 
496
            except KeyError:
 
497
                pass
 
498
            else:
 
499
                assert type == "commit"
 
500
                processed.add(revid)
 
501
        pending = set()
 
502
        for commit_sha in want:
 
503
            if commit_sha in have:
 
504
                continue
 
505
            (type, (revid, tree_sha)) = self._lookup_git_sha(commit_sha)
 
506
            assert type == "commit"
 
507
            pending.add(revid)
 
508
        todo = set()
 
509
        while pending:
 
510
            processed.update(pending)
 
511
            next_map = self.repository.get_parent_map(pending)
 
512
            next_pending = set()
 
513
            for item in next_map.iteritems():
 
514
                todo.add(item[0])
 
515
                next_pending.update(p for p in item[1] if p not in processed)
 
516
            pending = next_pending
 
517
        if NULL_REVISION in todo:
 
518
            todo.remove(NULL_REVISION)
 
519
        trace.mutter('sending revisions %r', todo)
 
520
        ret = []
 
521
        pb = ui.ui_factory.nested_progress_bar()
 
522
        try:
 
523
            for i, revid in enumerate(todo):
 
524
                pb.update("generating git objects", i, len(todo))
 
525
                rev = self.repository.get_revision(revid)
 
526
                tree = self.tree_cache.revision_tree(revid)
 
527
                for path, obj, ie in self._revision_to_objects(rev, tree):
 
528
                    ret.append((obj, path))
 
529
        finally:
 
530
            pb.finished()
 
531
        return ret
 
532
 
 
533
    def add_thin_pack(self):
 
534
        import tempfile
 
535
        import os
 
536
        fd, path = tempfile.mkstemp(suffix=".pack")
 
537
        f = os.fdopen(fd, 'wb')
 
538
        def commit():
 
539
            from dulwich.pack import PackData, Pack
 
540
            from bzrlib.plugins.git.fetch import import_git_objects
 
541
            os.fsync(fd)
 
542
            f.close()
 
543
            if os.path.getsize(path) == 0:
 
544
                return
 
545
            pd = PackData(path)
 
546
            pd.create_index_v2(path[:-5]+".idx", self.object_store.get_raw)
 
547
 
 
548
            p = Pack(path[:-5])
 
549
            self.repository.lock_write()
 
550
            try:
 
551
                self.repository.start_write_group()
 
552
                try:
 
553
                    import_git_objects(self.repository, self.mapping, 
 
554
                        p.iterobjects(get_raw=self.get_raw),
 
555
                        self.object_store)
 
556
                except:
 
557
                    self.repository.abort_write_group()
 
558
                    raise
 
559
                else:
 
560
                    self.repository.commit_write_group()
 
561
            finally:
 
562
                self.repository.unlock()
 
563
        return f, commit
 
564
 
 
565
    # The pack isn't kept around anyway, so no point 
 
566
    # in treating full packs different from thin packs
 
567
    add_pack = add_thin_pack