/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

Merge roundtrip support.

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
                return None
 
333
        return self.mapping.export_commit(rev, tree_sha, parent_lookup,
 
334
            roundtrip)
 
335
 
 
336
    def _create_fileid_map_blob(self, inv):
 
337
        # FIXME: This can probably be a lot more efficient, 
 
338
        # not all files necessarily have to be processed.
 
339
        file_ids = {}
 
340
        for (path, ie) in inv.iter_entries():
 
341
            if self.mapping.generate_file_id(path) != ie.file_id:
 
342
                file_ids[path] = ie.file_id
 
343
        return self.mapping.export_fileid_map(file_ids)
 
344
 
 
345
    def _revision_to_objects(self, rev, tree, roundtrip):
 
346
        """Convert a revision to a set of git objects.
 
347
 
 
348
        :param rev: Bazaar revision object
 
349
        :param tree: Bazaar revision tree
 
350
        :param roundtrip: Whether to roundtrip all Bazaar revision data
 
351
        """
 
352
        unusual_modes = extract_unusual_modes(rev)
 
353
        present_parents = self.repository.has_revisions(rev.parent_ids)
 
354
        parent_trees = self.tree_cache.revision_trees(
 
355
            [p for p in rev.parent_ids if p in present_parents])
 
356
        root_tree = None
 
357
        for path, obj, ie in _tree_to_objects(tree, parent_trees,
 
358
                self._cache.idmap, unusual_modes, self.mapping.BZR_DUMMY_FILE):
 
359
            if path == "":
 
360
                root_tree = obj
 
361
                root_ie = ie
 
362
                # Don't yield just yet
 
363
            else:
 
364
                yield path, obj, ie
 
365
        if root_tree is None:
 
366
            # Pointless commit - get the tree sha elsewhere
 
367
            if not rev.parent_ids:
 
368
                root_tree = Tree()
 
369
            else:
 
370
                base_sha1 = self._lookup_revision_sha1(rev.parent_ids[0])
 
371
                root_tree = self[self[base_sha1].tree]
 
372
            root_ie = tree.inventory.root
 
373
        if roundtrip:
 
374
            b = self._create_fileid_map_blob(tree.inventory)
 
375
            if b is not None:
 
376
                root_tree[self.mapping.BZR_FILE_IDS_FILE] = ((stat.S_IFREG | 0644), b.id)
 
377
                yield self.mapping.BZR_FILE_IDS_FILE, b, None
 
378
        yield "", root_tree, root_ie
 
379
        commit_obj = self._reconstruct_commit(rev, root_tree.id,
 
380
            roundtrip=roundtrip)
 
381
        try:
 
382
            foreign_revid, mapping = mapping_registry.parse_revision_id(
 
383
                rev.revision_id)
 
384
        except errors.InvalidRevisionId:
 
385
            pass
 
386
        else:
 
387
            _check_expected_sha(foreign_revid, commit_obj)
 
388
        yield None, commit_obj, None
 
389
 
 
390
    def _get_updater(self, rev):
 
391
        return self._cache.get_updater(rev)
59
392
 
60
393
    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)
 
394
        rev = self.repository.get_revision(revid)
 
395
        tree = self.tree_cache.revision_tree(rev.revision_id)
 
396
        updater = self._get_updater(rev)
 
397
        for path, obj, ie in self._revision_to_objects(rev, tree,
 
398
            roundtrip=True):
 
399
            updater.add_object(obj, ie)
 
400
        commit_obj = updater.finish()
 
401
        return commit_obj.id
 
402
 
 
403
    def _reconstruct_blobs(self, keys):
 
404
        """Return a Git Blob object from a fileid and revision stored in bzr.
 
405
 
 
406
        :param fileid: File id of the text
 
407
        :param revision: Revision of the text
 
408
        """
 
409
        stream = self.repository.iter_files_bytes(
 
410
            ((key[0], key[1], key) for key in keys))
 
411
        for (fileid, revision, expected_sha), chunks in stream:
 
412
            blob = Blob()
 
413
            blob.chunked = chunks
 
414
            if blob.id != expected_sha and blob.data == "":
 
415
                # Perhaps it's a symlink ?
 
416
                tree = self.tree_cache.revision_tree(revision)
 
417
                entry = tree.inventory[fileid]
 
418
                if entry.kind == 'symlink':
 
419
                    blob = symlink_to_blob(entry)
 
420
            _check_expected_sha(expected_sha, blob)
 
421
            yield blob
 
422
 
 
423
    def _reconstruct_tree(self, fileid, revid, inv, unusual_modes,
 
424
        expected_sha=None):
 
425
        """Return a Git Tree object from a file id and a revision stored in bzr.
 
426
 
 
427
        :param fileid: fileid in the tree.
 
428
        :param revision: Revision of the tree.
 
429
        """
 
430
        def get_ie_sha1(entry):
 
431
            if entry.kind == "directory":
 
432
                try:
 
433
                    return self._cache.idmap.lookup_tree_id(entry.file_id,
 
434
                        revid)
 
435
                except (NotImplementedError, KeyError):
 
436
                    obj = self._reconstruct_tree(entry.file_id, revid, inv,
 
437
                        unusual_modes)
 
438
                    if obj is None:
 
439
                        return None
 
440
                    else:
 
441
                        return obj.id
 
442
            elif entry.kind in ("file", "symlink"):
 
443
                try:
 
444
                    return self._cache.idmap.lookup_blob_id(entry.file_id,
 
445
                        entry.revision)
 
446
                except KeyError:
 
447
                    # no-change merge?
 
448
                    return self._reconstruct_blobs(
 
449
                        [(entry.file_id, entry.revision, None)]).next().id
 
450
            else:
 
451
                raise AssertionError("unknown entry kind '%s'" % entry.kind)
 
452
        tree = directory_to_tree(inv[fileid], get_ie_sha1, unusual_modes,
 
453
            self.mapping.BZR_DUMMY_FILE)
 
454
        if inv.root.file_id == fileid:
 
455
            b = self._create_fileid_map_blob(inv)
 
456
            # If this is the root tree, add the file ids
 
457
            tree[self.mapping.BZR_FILE_IDS_FILE] = ((stat.S_IFREG | 0644), b.id)
 
458
        _check_expected_sha(expected_sha, tree)
 
459
        return tree
 
460
 
 
461
    def get_parents(self, sha):
 
462
        """Retrieve the parents of a Git commit by SHA1.
 
463
 
 
464
        :param sha: SHA1 of the commit
 
465
        :raises: KeyError, NotCommitError
 
466
        """
 
467
        return self[sha].parents
 
468
 
 
469
    def _lookup_revision_sha1(self, revid):
 
470
        """Return the SHA1 matching a Bazaar revision."""
 
471
        from dulwich.protocol import ZERO_SHA
 
472
        if revid == NULL_REVISION:
 
473
            return ZERO_SHA
 
474
        try:
 
475
            return self._cache.idmap.lookup_commit(revid)
 
476
        except KeyError:
 
477
            try:
 
478
                return mapping_registry.parse_revision_id(revid)[0]
 
479
            except errors.InvalidRevisionId:
 
480
                self.repository.lock_read()
 
481
                try:
 
482
                    self._update_sha_map(revid)
 
483
                finally:
 
484
                    self.repository.unlock()
 
485
                return self._cache.idmap.lookup_commit(revid)
 
486
 
 
487
    def get_raw(self, sha):
 
488
        """Get the raw representation of a Git object by SHA1.
 
489
 
 
490
        :param sha: SHA1 of the git object
 
491
        """
 
492
        obj = self[sha]
 
493
        return (obj.type, obj.as_raw_string())
 
494
 
 
495
    def __contains__(self, sha):
 
496
        # See if sha is in map
 
497
        try:
 
498
            (type, type_data) = self.lookup_git_sha(sha)
 
499
            if type == "commit":
 
500
                return self.repository.has_revision(type_data[0])
 
501
            elif type == "blob":
 
502
                return self.repository.texts.has_version(type_data)
 
503
            elif type == "tree":
 
504
                return self.repository.has_revision(type_data[1])
 
505
            else:
 
506
                raise AssertionError("Unknown object type '%s'" % type)
 
507
        except KeyError:
 
508
            return False
 
509
 
 
510
    def lookup_git_shas(self, shas, update_map=True):
 
511
        ret = {}
 
512
        for sha in shas:
 
513
            try:
 
514
                ret[sha] = self._cache.idmap.lookup_git_sha(sha)
 
515
            except KeyError:
 
516
                if update_map:
 
517
                    # if not, see if there are any unconverted revisions and add
 
518
                    # them to the map, search for sha in map again
 
519
                    self._update_sha_map()
 
520
                    update_map = False
 
521
                    try:
 
522
                        ret[sha] = self._cache.idmap.lookup_git_sha(sha)
 
523
                    except KeyError:
 
524
                        pass
 
525
        return ret
 
526
 
 
527
    def lookup_git_sha(self, sha, update_map=True):
 
528
        return self.lookup_git_shas([sha], update_map=update_map)[sha]
87
529
 
88
530
    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)
 
531
        if self._cache.content_cache is not None:
 
532
            try:
 
533
                return self._cache.content_cache[sha]
 
534
            except KeyError:
 
535
                pass
 
536
        (type, type_data) = self.lookup_git_sha(sha)
97
537
        # convert object to git object
98
538
        if type == "commit":
99
 
            return self._get_commit(*type_data)
 
539
            (revid, tree_sha) = type_data
 
540
            try:
 
541
                rev = self.repository.get_revision(revid)
 
542
            except errors.NoSuchRevision:
 
543
                trace.mutter('entry for %s %s in shamap: %r, but not found in '
 
544
                             'repository', type, sha, type_data)
 
545
                raise KeyError(sha)
 
546
            commit = self._reconstruct_commit(rev, tree_sha, roundtrip=True)
 
547
            _check_expected_sha(sha, commit)
 
548
            return commit
100
549
        elif type == "blob":
101
 
            return self._get_blob(*type_data)
 
550
            (fileid, revision) = type_data
 
551
            return self._reconstruct_blobs([(fileid, revision, sha)]).next()
102
552
        elif type == "tree":
103
 
            return self._get_tree(*type_data)
 
553
            (fileid, revid) = type_data
 
554
            try:
 
555
                tree = self.tree_cache.revision_tree(revid)
 
556
                rev = self.repository.get_revision(revid)
 
557
            except errors.NoSuchRevision:
 
558
                trace.mutter('entry for %s %s in shamap: %r, but not found in repository', type, sha, type_data)
 
559
                raise KeyError(sha)
 
560
            unusual_modes = extract_unusual_modes(rev)
 
561
            try:
 
562
                return self._reconstruct_tree(fileid, revid, tree.inventory,
 
563
                    unusual_modes, expected_sha=sha)
 
564
            except errors.NoSuchRevision:
 
565
                raise KeyError(sha)
104
566
        else:
105
567
            raise AssertionError("Unknown object type '%s'" % type)
 
568
 
 
569
    def generate_lossy_pack_contents(self, have, want, progress=None,
 
570
            get_tagged=None):
 
571
        return self.generate_pack_contents(have, want, progress, get_tagged,
 
572
            lossy=True)
 
573
 
 
574
    def generate_pack_contents(self, have, want, progress=None,
 
575
            get_tagged=None, lossy=False):
 
576
        """Iterate over the contents of a pack file.
 
577
 
 
578
        :param have: List of SHA1s of objects that should not be sent
 
579
        :param want: List of SHA1s of objects that should be sent
 
580
        """
 
581
        processed = set()
 
582
        ret = self.lookup_git_shas(have + want)
 
583
        for commit_sha in have:
 
584
            try:
 
585
                (type, (revid, tree_sha)) = ret[commit_sha]
 
586
            except KeyError:
 
587
                pass
 
588
            else:
 
589
                assert type == "commit"
 
590
                processed.add(revid)
 
591
        pending = set()
 
592
        for commit_sha in want:
 
593
            if commit_sha in have:
 
594
                continue
 
595
            try:
 
596
                (type, (revid, tree_sha)) = ret[commit_sha]
 
597
            except KeyError:
 
598
                pass
 
599
            else:
 
600
                assert type == "commit"
 
601
                pending.add(revid)
 
602
 
 
603
        todo = _find_missing_bzr_revids(self.repository.get_parent_map, 
 
604
                                        pending, processed)
 
605
        trace.mutter('sending revisions %r', todo)
 
606
        ret = []
 
607
        pb = ui.ui_factory.nested_progress_bar()
 
608
        try:
 
609
            for i, revid in enumerate(todo):
 
610
                pb.update("generating git objects", i, len(todo))
 
611
                rev = self.repository.get_revision(revid)
 
612
                tree = self.tree_cache.revision_tree(revid)
 
613
                for path, obj, ie in self._revision_to_objects(rev, tree,
 
614
                    roundtrip=not lossy):
 
615
                    ret.append((obj, path))
 
616
        finally:
 
617
            pb.finished()
 
618
        return ret
 
619
 
 
620
    def add_thin_pack(self):
 
621
        import tempfile
 
622
        import os
 
623
        fd, path = tempfile.mkstemp(suffix=".pack")
 
624
        f = os.fdopen(fd, 'wb')
 
625
        def commit():
 
626
            from dulwich.pack import PackData, Pack
 
627
            from bzrlib.plugins.git.fetch import import_git_objects
 
628
            os.fsync(fd)
 
629
            f.close()
 
630
            if os.path.getsize(path) == 0:
 
631
                return
 
632
            pd = PackData(path)
 
633
            pd.create_index_v2(path[:-5]+".idx", self.object_store.get_raw)
 
634
 
 
635
            p = Pack(path[:-5])
 
636
            self.repository.lock_write()
 
637
            try:
 
638
                self.repository.start_write_group()
 
639
                try:
 
640
                    import_git_objects(self.repository, self.mapping, 
 
641
                        p.iterobjects(get_raw=self.get_raw),
 
642
                        self.object_store)
 
643
                except:
 
644
                    self.repository.abort_write_group()
 
645
                    raise
 
646
                else:
 
647
                    self.repository.commit_write_group()
 
648
            finally:
 
649
                self.repository.unlock()
 
650
        return f, commit
 
651
 
 
652
    # The pack isn't kept around anyway, so no point 
 
653
    # in treating full packs different from thin packs
 
654
    add_pack = add_thin_pack