/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

Fix determining of unusual file modes.

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