/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 function for verifying reconstruction of objects still works.

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
            if ie.kind in ("file", "symlink"):
 
208
                try:
 
209
                    return idmap.lookup_blob_id(ie.file_id, ie.revision)
 
210
                except KeyError:
 
211
                    # no-change merge ?
 
212
                    blob = Blob()
 
213
                    blob.data = tree.get_file_text(ie.file_id)
 
214
                    return blob.id
 
215
            elif ie.kind == "directory":
 
216
                # Not all cache backends store the tree information, 
 
217
                # calculate again from scratch
 
218
                ret = directory_to_tree(ie, ie_to_hexsha, unusual_modes)
 
219
                if ret is None:
 
220
                    return ret
 
221
                return ret.id
 
222
            else:
 
223
                raise AssertionError
 
224
 
 
225
    for path in sorted(trees.keys(), reverse=True):
 
226
        ie = tree.inventory[trees[path]]
 
227
        assert ie.kind == "directory"
 
228
        obj = directory_to_tree(ie, ie_to_hexsha, unusual_modes)
 
229
        if obj is not None:
 
230
            yield path, obj, ie
 
231
            shamap[ie.file_id] = obj.id
 
232
 
 
233
 
 
234
class BazaarObjectStore(BaseObjectStore):
 
235
    """A Git-style object store backed onto a Bazaar repository."""
37
236
 
38
237
    def __init__(self, repository, mapping=None):
39
238
        self.repository = repository
40
239
        if mapping is None:
41
 
            self.mapping = self.repository.get_mapping()
 
240
            self.mapping = default_mapping
42
241
        else:
43
242
            self.mapping = mapping
44
 
        self._idmap = GitShaMap(self.repository._transport)
 
243
        self._cache = cache_from_repository(repository)
 
244
        self._content_cache_types = ("tree")
 
245
        self.start_write_group = self._cache.idmap.start_write_group
 
246
        self.abort_write_group = self._cache.idmap.abort_write_group
 
247
        self.commit_write_group = self._cache.idmap.commit_write_group
 
248
        self.tree_cache = LRUTreeCache(self.repository)
45
249
 
46
 
    def _update_sha_map(self):
47
 
        all_revids = self.repository.all_revision_ids()
 
250
    def _update_sha_map(self, stop_revision=None):
48
251
        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()
 
252
        if stop_revision is None:
 
253
            heads = graph.heads(self.repository.all_revision_ids())
 
254
        else:
 
255
            heads = set([stop_revision])
 
256
        missing_revids = self._cache.idmap.missing_revisions(heads)
 
257
        while heads:
 
258
            parents = graph.get_parent_map(heads)
 
259
            todo = set()
 
260
            for p in parents.values():
 
261
                todo.update([x for x in p if x not in missing_revids])
 
262
            heads = self._cache.idmap.missing_revisions(todo)
 
263
            missing_revids.update(heads)
 
264
        if NULL_REVISION in missing_revids:
 
265
            missing_revids.remove(NULL_REVISION)
 
266
        missing_revids = self.repository.has_revisions(missing_revids)
 
267
        if not missing_revids:
 
268
            return
 
269
        self.start_write_group()
 
270
        try:
 
271
            pb = ui.ui_factory.nested_progress_bar()
 
272
            try:
 
273
                for i, revid in enumerate(graph.iter_topo_order(missing_revids)):
 
274
                    trace.mutter('processing %r', revid)
 
275
                    pb.update("updating git map", i, len(missing_revids))
 
276
                    self._update_sha_map_revision(revid)
 
277
            finally:
 
278
                pb.finished()
 
279
        except:
 
280
            self.abort_write_group()
 
281
            raise
 
282
        else:
 
283
            self.commit_write_group()
 
284
 
 
285
    def __iter__(self):
 
286
        self._update_sha_map()
 
287
        return iter(self._cache.idmap.sha1s())
 
288
 
 
289
    def _reconstruct_commit(self, rev, tree_sha):
 
290
        def parent_lookup(revid):
 
291
            try:
 
292
                return self._lookup_revision_sha1(revid)
 
293
            except errors.NoSuchRevision:
 
294
                trace.warning("Ignoring ghost parent %s", revid)
 
295
                return None
 
296
        return self.mapping.export_commit(rev, tree_sha, parent_lookup)
 
297
 
 
298
    def _revision_to_objects(self, rev, tree):
 
299
        unusual_modes = extract_unusual_modes(rev)
 
300
        present_parents = self.repository.has_revisions(rev.parent_ids)
 
301
        parent_trees = self.tree_cache.revision_trees(
 
302
            [p for p in rev.parent_ids if p in present_parents])
 
303
        tree_sha = None
 
304
        for path, obj, ie in _tree_to_objects(tree, parent_trees,
 
305
                self._cache.idmap, unusual_modes):
 
306
            yield path, obj, ie
 
307
            if path == "":
 
308
                tree_sha = obj.id
 
309
        if tree_sha is None:
 
310
            # Pointless commit - get the tree sha elsewhere
 
311
            if not rev.parent_ids:
 
312
                tree_sha = Tree().id
 
313
            else:
 
314
                base_sha1 = self._lookup_revision_sha1(rev.parent_ids[0])
 
315
                tree_sha = self[base_sha1].tree
 
316
        commit_obj = self._reconstruct_commit(rev, tree_sha)
 
317
        try:
 
318
            foreign_revid, mapping = mapping_registry.parse_revision_id(
 
319
                rev.revision_id)
 
320
        except errors.InvalidRevisionId:
 
321
            pass
 
322
        else:
 
323
            _check_expected_sha(foreign_revid, commit_obj)
 
324
        yield None, commit_obj, None
 
325
 
 
326
    def _get_updater(self, rev):
 
327
        return self._cache.get_updater(rev)
59
328
 
60
329
    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 
 
330
        rev = self.repository.get_revision(revid)
 
331
        tree = self.tree_cache.revision_tree(rev.revision_id)
 
332
        updater = self._get_updater(rev)
 
333
        for path, obj, ie in self._revision_to_objects(rev, tree):
 
334
            updater.add_object(obj, ie)
 
335
        commit_obj = updater.finish()
 
336
        return commit_obj.id
 
337
 
 
338
    def _reconstruct_blobs(self, keys):
 
339
        """Return a Git Blob object from a fileid and revision stored in bzr.
 
340
 
 
341
        :param fileid: File id of the text
 
342
        :param revision: Revision of the text
 
343
        """
 
344
        stream = self.repository.iter_files_bytes(
 
345
            ((key[0], key[1], key) for key in keys))
 
346
        for (fileid, revision, expected_sha), chunks in stream:
 
347
            blob = Blob()
 
348
            blob.chunked = chunks
 
349
            if blob.id != expected_sha and blob.data == "":
 
350
                # Perhaps it's a symlink ?
 
351
                tree = self.tree_cache.revision_tree(revision)
 
352
                entry = tree.inventory[fileid]
 
353
                if entry.kind == 'symlink':
 
354
                    blob = symlink_to_blob(entry)
 
355
            _check_expected_sha(expected_sha, blob)
 
356
            yield blob
 
357
 
 
358
    def _reconstruct_tree(self, fileid, revid, inv, unusual_modes,
 
359
        expected_sha=None):
 
360
        """Return a Git Tree object from a file id and a revision stored in bzr.
 
361
 
 
362
        :param fileid: fileid in the tree.
 
363
        :param revision: Revision of the tree.
 
364
        """
 
365
        def get_ie_sha1(entry):
 
366
            if entry.kind == "directory":
 
367
                try:
 
368
                    return self._cache.idmap.lookup_tree_id(entry.file_id,
 
369
                        revid)
 
370
                except (NotImplementedError, KeyError):
 
371
                    obj = self._reconstruct_tree(entry.file_id, revid, inv,
 
372
                        unusual_modes)
 
373
                    if obj is None:
 
374
                        return None
 
375
                    else:
 
376
                        return obj.id
 
377
            elif entry.kind in ("file", "symlink"):
 
378
                try:
 
379
                    return self._cache.idmap.lookup_blob_id(entry.file_id,
 
380
                        entry.revision)
 
381
                except KeyError:
 
382
                    # no-change merge?
 
383
                    return self._reconstruct_blobs(
 
384
                        [(entry.file_id, entry.revision, None)]).next().id
 
385
            else:
 
386
                raise AssertionError("unknown entry kind '%s'" % entry.kind)
 
387
        tree = directory_to_tree(inv[fileid], get_ie_sha1, unusual_modes)
 
388
        _check_expected_sha(expected_sha, tree)
 
389
        return tree
 
390
 
 
391
    def get_parents(self, sha):
 
392
        """Retrieve the parents of a Git commit by SHA1.
 
393
 
 
394
        :param sha: SHA1 of the commit
 
395
        :raises: KeyError, NotCommitError
 
396
        """
 
397
        return self[sha].parents
 
398
 
 
399
    def _lookup_revision_sha1(self, revid):
 
400
        """Return the SHA1 matching a Bazaar revision."""
 
401
        if revid == NULL_REVISION:
 
402
            return "0" * 40
 
403
        try:
 
404
            return self._cache.idmap.lookup_commit(revid)
 
405
        except KeyError:
 
406
            try:
 
407
                return mapping_registry.parse_revision_id(revid)[0]
 
408
            except errors.InvalidRevisionId:
 
409
                self._update_sha_map(revid)
 
410
                return self._cache.idmap.lookup_commit(revid)
 
411
 
 
412
    def get_raw(self, sha):
 
413
        """Get the raw representation of a Git object by SHA1.
 
414
 
 
415
        :param sha: SHA1 of the git object
 
416
        """
 
417
        obj = self[sha]
 
418
        return (obj.type, obj.as_raw_string())
 
419
 
 
420
    def __contains__(self, sha):
 
421
        # See if sha is in map
 
422
        try:
 
423
            (type, type_data) = self._lookup_git_sha(sha)
 
424
            if type == "commit":
 
425
                return self.repository.has_revision(type_data[0])
 
426
            elif type == "blob":
 
427
                return self.repository.texts.has_version(type_data)
 
428
            elif type == "tree":
 
429
                return self.repository.has_revision(type_data[1])
 
430
            else:
 
431
                raise AssertionError("Unknown object type '%s'" % type)
 
432
        except KeyError:
 
433
            return False
 
434
 
 
435
    def _lookup_git_sha(self, sha):
 
436
        # See if sha is in map
 
437
        try:
 
438
            return self._cache.idmap.lookup_git_sha(sha)
 
439
        except KeyError:
 
440
            # if not, see if there are any unconverted revisions and add them
94
441
            # to the map, search for sha in map again
95
442
            self._update_sha_map()
96
 
            (type, type_data) = self._idmap.lookup_git_sha(sha)
 
443
            return self._cache.idmap.lookup_git_sha(sha)
 
444
 
 
445
    def __getitem__(self, sha):
 
446
        if self._cache.content_cache is not None:
 
447
            try:
 
448
                return self._cache.content_cache[sha]
 
449
            except KeyError:
 
450
                pass
 
451
        (type, type_data) = self._lookup_git_sha(sha)
97
452
        # convert object to git object
98
453
        if type == "commit":
99
 
            return self._get_commit(*type_data)
 
454
            (revid, tree_sha) = type_data
 
455
            try:
 
456
                rev = self.repository.get_revision(revid)
 
457
            except errors.NoSuchRevision:
 
458
                trace.mutter('entry for %s %s in shamap: %r, but not found in '
 
459
                             'repository', type, sha, type_data)
 
460
                raise KeyError(sha)
 
461
            commit = self._reconstruct_commit(rev, tree_sha)
 
462
            _check_expected_sha(sha, commit)
 
463
            return commit
100
464
        elif type == "blob":
101
 
            return self._get_blob(*type_data)
 
465
            (fileid, revision) = type_data
 
466
            return self._reconstruct_blobs([(fileid, revision, sha)]).next()
102
467
        elif type == "tree":
103
 
            return self._get_tree(*type_data)
 
468
            (fileid, revid) = type_data
 
469
            try:
 
470
                tree = self.tree_cache.revision_tree(revid)
 
471
                rev = self.repository.get_revision(revid)
 
472
            except errors.NoSuchRevision:
 
473
                trace.mutter('entry for %s %s in shamap: %r, but not found in repository', type, sha, type_data)
 
474
                raise KeyError(sha)
 
475
            unusual_modes = extract_unusual_modes(rev)
 
476
            try:
 
477
                return self._reconstruct_tree(fileid, revid, tree.inventory,
 
478
                    unusual_modes, expected_sha=sha)
 
479
            except errors.NoSuchRevision:
 
480
                raise KeyError(sha)
104
481
        else:
105
482
            raise AssertionError("Unknown object type '%s'" % type)
 
483
 
 
484
    def generate_pack_contents(self, have, want, progress=None, get_tagged=None):
 
485
        """Iterate over the contents of a pack file.
 
486
 
 
487
        :param have: List of SHA1s of objects that should not be sent
 
488
        :param want: List of SHA1s of objects that should be sent
 
489
        """
 
490
        processed = set()
 
491
        for commit_sha in have:
 
492
            try:
 
493
                (type, (revid, tree_sha)) = self._lookup_git_sha(commit_sha)
 
494
            except KeyError:
 
495
                pass
 
496
            else:
 
497
                assert type == "commit"
 
498
                processed.add(revid)
 
499
        pending = set()
 
500
        for commit_sha in want:
 
501
            if commit_sha in have:
 
502
                continue
 
503
            (type, (revid, tree_sha)) = self._lookup_git_sha(commit_sha)
 
504
            assert type == "commit"
 
505
            pending.add(revid)
 
506
        todo = set()
 
507
        while pending:
 
508
            processed.update(pending)
 
509
            next_map = self.repository.get_parent_map(pending)
 
510
            next_pending = set()
 
511
            for item in next_map.iteritems():
 
512
                todo.add(item[0])
 
513
                next_pending.update(p for p in item[1] if p not in processed)
 
514
            pending = next_pending
 
515
        if NULL_REVISION in todo:
 
516
            todo.remove(NULL_REVISION)
 
517
        trace.mutter('sending revisions %r', todo)
 
518
        ret = []
 
519
        pb = ui.ui_factory.nested_progress_bar()
 
520
        try:
 
521
            for i, revid in enumerate(todo):
 
522
                pb.update("generating git objects", i, len(todo))
 
523
                rev = self.repository.get_revision(revid)
 
524
                tree = self.tree_cache.revision_tree(revid)
 
525
                for path, obj, ie in self._revision_to_objects(rev, tree):
 
526
                    ret.append((obj, path))
 
527
        finally:
 
528
            pb.finished()
 
529
        return ret
 
530
 
 
531
    def add_thin_pack(self):
 
532
        import tempfile
 
533
        import os
 
534
        fd, path = tempfile.mkstemp(suffix=".pack")
 
535
        f = os.fdopen(fd, 'wb')
 
536
        def commit():
 
537
            from dulwich.pack import PackData, Pack
 
538
            from bzrlib.plugins.git.fetch import import_git_objects
 
539
            os.fsync(fd)
 
540
            f.close()
 
541
            if os.path.getsize(path) == 0:
 
542
                return
 
543
            pd = PackData(path)
 
544
            pd.create_index_v2(path[:-5]+".idx", self.object_store.get_raw)
 
545
 
 
546
            p = Pack(path[:-5])
 
547
            self.repository.lock_write()
 
548
            try:
 
549
                self.repository.start_write_group()
 
550
                try:
 
551
                    import_git_objects(self.repository, self.mapping, 
 
552
                        p.iterobjects(get_raw=self.get_raw),
 
553
                        self.object_store)
 
554
                except:
 
555
                    self.repository.abort_write_group()
 
556
                    raise
 
557
                else:
 
558
                    self.repository.commit_write_group()
 
559
            finally:
 
560
                self.repository.unlock()
 
561
        return f, commit
 
562
 
 
563
    # The pack isn't kept around anyway, so no point 
 
564
    # in treating full packs different from thin packs
 
565
    add_pack = add_thin_pack