/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

  • Committer: Jelmer Vernooij
  • Date: 2010-05-13 09:40:48 UTC
  • mto: (0.200.912 trunk)
  • mto: This revision was merged to the branch mainline in revision 6960.
  • Revision ID: jelmer@samba.org-20100513094048-ntsaywuymtmcufai
Fix Repository.all_revision_ids.

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