/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 Repository.has_revision{s,}.

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