/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

Mark git formats as supporting colocated branches.

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
        if revid == NULL_REVISION:
 
403
            return "0" * 40
 
404
        try:
 
405
            return self._cache.idmap.lookup_commit(revid)
 
406
        except KeyError:
 
407
            try:
 
408
                return mapping_registry.parse_revision_id(revid)[0]
 
409
            except errors.InvalidRevisionId:
 
410
                self._update_sha_map(revid)
 
411
                return self._cache.idmap.lookup_commit(revid)
 
412
 
 
413
    def get_raw(self, sha):
 
414
        """Get the raw representation of a Git object by SHA1.
 
415
 
 
416
        :param sha: SHA1 of the git object
 
417
        """
 
418
        obj = self[sha]
 
419
        return (obj.type, obj.as_raw_string())
 
420
 
 
421
    def __contains__(self, sha):
 
422
        # See if sha is in map
 
423
        try:
 
424
            (type, type_data) = self._lookup_git_sha(sha)
 
425
            if type == "commit":
 
426
                return self.repository.has_revision(type_data[0])
 
427
            elif type == "blob":
 
428
                return self.repository.texts.has_version(type_data)
 
429
            elif type == "tree":
 
430
                return self.repository.has_revision(type_data[1])
 
431
            else:
 
432
                raise AssertionError("Unknown object type '%s'" % type)
 
433
        except KeyError:
 
434
            return False
 
435
 
 
436
    def _lookup_git_sha(self, sha):
 
437
        # See if sha is in map
 
438
        try:
 
439
            return self._cache.idmap.lookup_git_sha(sha)
 
440
        except KeyError:
 
441
            # if not, see if there are any unconverted revisions and add them
94
442
            # to the map, search for sha in map again
95
443
            self._update_sha_map()
96
 
            (type, type_data) = self._idmap.lookup_git_sha(sha)
 
444
            return self._cache.idmap.lookup_git_sha(sha)
 
445
 
 
446
    def __getitem__(self, sha):
 
447
        if self._cache.content_cache is not None:
 
448
            try:
 
449
                return self._cache.content_cache[sha]
 
450
            except KeyError:
 
451
                pass
 
452
        (type, type_data) = self._lookup_git_sha(sha)
97
453
        # convert object to git object
98
454
        if type == "commit":
99
 
            return self._get_commit(*type_data)
 
455
            (revid, tree_sha) = type_data
 
456
            try:
 
457
                rev = self.repository.get_revision(revid)
 
458
            except errors.NoSuchRevision:
 
459
                trace.mutter('entry for %s %s in shamap: %r, but not found in '
 
460
                             'repository', type, sha, type_data)
 
461
                raise KeyError(sha)
 
462
            commit = self._reconstruct_commit(rev, tree_sha)
 
463
            _check_expected_sha(sha, commit)
 
464
            return commit
100
465
        elif type == "blob":
101
 
            return self._get_blob(*type_data)
 
466
            (fileid, revision) = type_data
 
467
            return self._reconstruct_blobs([(fileid, revision, sha)]).next()
102
468
        elif type == "tree":
103
 
            return self._get_tree(*type_data)
 
469
            (fileid, revid) = type_data
 
470
            try:
 
471
                tree = self.tree_cache.revision_tree(revid)
 
472
                rev = self.repository.get_revision(revid)
 
473
            except errors.NoSuchRevision:
 
474
                trace.mutter('entry for %s %s in shamap: %r, but not found in repository', type, sha, type_data)
 
475
                raise KeyError(sha)
 
476
            unusual_modes = extract_unusual_modes(rev)
 
477
            try:
 
478
                return self._reconstruct_tree(fileid, revid, tree.inventory,
 
479
                    unusual_modes, expected_sha=sha)
 
480
            except errors.NoSuchRevision:
 
481
                raise KeyError(sha)
104
482
        else:
105
483
            raise AssertionError("Unknown object type '%s'" % type)
 
484
 
 
485
    def generate_pack_contents(self, have, want, progress=None, get_tagged=None):
 
486
        """Iterate over the contents of a pack file.
 
487
 
 
488
        :param have: List of SHA1s of objects that should not be sent
 
489
        :param want: List of SHA1s of objects that should be sent
 
490
        """
 
491
        processed = set()
 
492
        for commit_sha in have:
 
493
            try:
 
494
                (type, (revid, tree_sha)) = self._lookup_git_sha(commit_sha)
 
495
            except KeyError:
 
496
                pass
 
497
            else:
 
498
                assert type == "commit"
 
499
                processed.add(revid)
 
500
        pending = set()
 
501
        for commit_sha in want:
 
502
            if commit_sha in have:
 
503
                continue
 
504
            (type, (revid, tree_sha)) = self._lookup_git_sha(commit_sha)
 
505
            assert type == "commit"
 
506
            pending.add(revid)
 
507
        todo = set()
 
508
        while pending:
 
509
            processed.update(pending)
 
510
            next_map = self.repository.get_parent_map(pending)
 
511
            next_pending = set()
 
512
            for item in next_map.iteritems():
 
513
                todo.add(item[0])
 
514
                next_pending.update(p for p in item[1] if p not in processed)
 
515
            pending = next_pending
 
516
        if NULL_REVISION in todo:
 
517
            todo.remove(NULL_REVISION)
 
518
        trace.mutter('sending revisions %r', todo)
 
519
        ret = []
 
520
        pb = ui.ui_factory.nested_progress_bar()
 
521
        try:
 
522
            for i, revid in enumerate(todo):
 
523
                pb.update("generating git objects", i, len(todo))
 
524
                rev = self.repository.get_revision(revid)
 
525
                tree = self.tree_cache.revision_tree(revid)
 
526
                for path, obj, ie in self._revision_to_objects(rev, tree):
 
527
                    ret.append((obj, path))
 
528
        finally:
 
529
            pb.finished()
 
530
        return ret
 
531
 
 
532
    def add_thin_pack(self):
 
533
        import tempfile
 
534
        import os
 
535
        fd, path = tempfile.mkstemp(suffix=".pack")
 
536
        f = os.fdopen(fd, 'wb')
 
537
        def commit():
 
538
            from dulwich.pack import PackData, Pack
 
539
            from bzrlib.plugins.git.fetch import import_git_objects
 
540
            os.fsync(fd)
 
541
            f.close()
 
542
            if os.path.getsize(path) == 0:
 
543
                return
 
544
            pd = PackData(path)
 
545
            pd.create_index_v2(path[:-5]+".idx", self.object_store.get_raw)
 
546
 
 
547
            p = Pack(path[:-5])
 
548
            self.repository.lock_write()
 
549
            try:
 
550
                self.repository.start_write_group()
 
551
                try:
 
552
                    import_git_objects(self.repository, self.mapping, 
 
553
                        p.iterobjects(get_raw=self.get_raw),
 
554
                        self.object_store)
 
555
                except:
 
556
                    self.repository.abort_write_group()
 
557
                    raise
 
558
                else:
 
559
                    self.repository.commit_write_group()
 
560
            finally:
 
561
                self.repository.unlock()
 
562
        return f, commit
 
563
 
 
564
    # The pack isn't kept around anyway, so no point 
 
565
    # in treating full packs different from thin packs
 
566
    add_pack = add_thin_pack