/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

SupportĀ tagĀ refs.

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