/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

Eliminate InventorySHAMap.

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