/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

Remove explicit use of rich root formats.

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 and self.mapping.BZR_FILE_IDS_FILE is not None:
 
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 and
 
455
            self.mapping.BZR_FILE_IDS_FILE is not None):
 
456
            b = self._create_fileid_map_blob(inv)
 
457
            # If this is the root tree, add the file ids
 
458
            tree[self.mapping.BZR_FILE_IDS_FILE] = ((stat.S_IFREG | 0644), b.id)
 
459
        _check_expected_sha(expected_sha, tree)
 
460
        return tree
 
461
 
 
462
    def get_parents(self, sha):
 
463
        """Retrieve the parents of a Git commit by SHA1.
 
464
 
 
465
        :param sha: SHA1 of the commit
 
466
        :raises: KeyError, NotCommitError
 
467
        """
 
468
        return self[sha].parents
 
469
 
 
470
    def _lookup_revision_sha1(self, revid):
 
471
        """Return the SHA1 matching a Bazaar revision."""
 
472
        from dulwich.protocol import ZERO_SHA
 
473
        if revid == NULL_REVISION:
 
474
            return ZERO_SHA
 
475
        try:
 
476
            return self._cache.idmap.lookup_commit(revid)
 
477
        except KeyError:
 
478
            try:
 
479
                return mapping_registry.parse_revision_id(revid)[0]
 
480
            except errors.InvalidRevisionId:
 
481
                self.repository.lock_read()
 
482
                try:
 
483
                    self._update_sha_map(revid)
 
484
                finally:
 
485
                    self.repository.unlock()
 
486
                return self._cache.idmap.lookup_commit(revid)
 
487
 
 
488
    def get_raw(self, sha):
 
489
        """Get the raw representation of a Git object by SHA1.
 
490
 
 
491
        :param sha: SHA1 of the git object
 
492
        """
 
493
        obj = self[sha]
 
494
        return (obj.type, obj.as_raw_string())
 
495
 
 
496
    def __contains__(self, sha):
 
497
        # See if sha is in map
 
498
        try:
 
499
            (type, type_data) = self.lookup_git_sha(sha)
 
500
            if type == "commit":
 
501
                return self.repository.has_revision(type_data[0])
 
502
            elif type == "blob":
 
503
                return self.repository.texts.has_version(type_data)
 
504
            elif type == "tree":
 
505
                return self.repository.has_revision(type_data[1])
 
506
            else:
 
507
                raise AssertionError("Unknown object type '%s'" % type)
 
508
        except KeyError:
 
509
            return False
 
510
 
 
511
    def lookup_git_shas(self, shas, update_map=True):
 
512
        ret = {}
 
513
        for sha in shas:
 
514
            try:
 
515
                ret[sha] = self._cache.idmap.lookup_git_sha(sha)
 
516
            except KeyError:
 
517
                if update_map:
 
518
                    # if not, see if there are any unconverted revisions and add
 
519
                    # them to the map, search for sha in map again
 
520
                    self._update_sha_map()
 
521
                    update_map = False
 
522
                    try:
 
523
                        ret[sha] = self._cache.idmap.lookup_git_sha(sha)
 
524
                    except KeyError:
 
525
                        pass
 
526
        return ret
 
527
 
 
528
    def lookup_git_sha(self, sha, update_map=True):
 
529
        return self.lookup_git_shas([sha], update_map=update_map)[sha]
87
530
 
88
531
    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)
 
532
        if self._cache.content_cache is not None:
 
533
            try:
 
534
                return self._cache.content_cache[sha]
 
535
            except KeyError:
 
536
                pass
 
537
        (type, type_data) = self.lookup_git_sha(sha)
97
538
        # convert object to git object
98
539
        if type == "commit":
99
 
            return self._get_commit(*type_data)
 
540
            (revid, tree_sha) = type_data
 
541
            try:
 
542
                rev = self.repository.get_revision(revid)
 
543
            except errors.NoSuchRevision:
 
544
                trace.mutter('entry for %s %s in shamap: %r, but not found in '
 
545
                             'repository', type, sha, type_data)
 
546
                raise KeyError(sha)
 
547
            commit = self._reconstruct_commit(rev, tree_sha, roundtrip=True)
 
548
            _check_expected_sha(sha, commit)
 
549
            return commit
100
550
        elif type == "blob":
101
 
            return self._get_blob(*type_data)
 
551
            (fileid, revision) = type_data
 
552
            return self._reconstruct_blobs([(fileid, revision, sha)]).next()
102
553
        elif type == "tree":
103
 
            return self._get_tree(*type_data)
 
554
            (fileid, revid) = type_data
 
555
            try:
 
556
                tree = self.tree_cache.revision_tree(revid)
 
557
                rev = self.repository.get_revision(revid)
 
558
            except errors.NoSuchRevision:
 
559
                trace.mutter('entry for %s %s in shamap: %r, but not found in repository', type, sha, type_data)
 
560
                raise KeyError(sha)
 
561
            unusual_modes = extract_unusual_modes(rev)
 
562
            try:
 
563
                return self._reconstruct_tree(fileid, revid, tree.inventory,
 
564
                    unusual_modes, expected_sha=sha)
 
565
            except errors.NoSuchRevision:
 
566
                raise KeyError(sha)
104
567
        else:
105
568
            raise AssertionError("Unknown object type '%s'" % type)
 
569
 
 
570
    def generate_lossy_pack_contents(self, have, want, progress=None,
 
571
            get_tagged=None):
 
572
        return self.generate_pack_contents(have, want, progress, get_tagged,
 
573
            lossy=True)
 
574
 
 
575
    def generate_pack_contents(self, have, want, progress=None,
 
576
            get_tagged=None, lossy=False):
 
577
        """Iterate over the contents of a pack file.
 
578
 
 
579
        :param have: List of SHA1s of objects that should not be sent
 
580
        :param want: List of SHA1s of objects that should be sent
 
581
        """
 
582
        processed = set()
 
583
        ret = self.lookup_git_shas(have + want)
 
584
        for commit_sha in have:
 
585
            try:
 
586
                (type, (revid, tree_sha)) = ret[commit_sha]
 
587
            except KeyError:
 
588
                pass
 
589
            else:
 
590
                assert type == "commit"
 
591
                processed.add(revid)
 
592
        pending = set()
 
593
        for commit_sha in want:
 
594
            if commit_sha in have:
 
595
                continue
 
596
            try:
 
597
                (type, (revid, tree_sha)) = ret[commit_sha]
 
598
            except KeyError:
 
599
                pass
 
600
            else:
 
601
                assert type == "commit"
 
602
                pending.add(revid)
 
603
 
 
604
        todo = _find_missing_bzr_revids(self.repository.get_parent_map, 
 
605
                                        pending, processed)
 
606
        trace.mutter('sending revisions %r', todo)
 
607
        ret = []
 
608
        pb = ui.ui_factory.nested_progress_bar()
 
609
        try:
 
610
            for i, revid in enumerate(todo):
 
611
                pb.update("generating git objects", i, len(todo))
 
612
                rev = self.repository.get_revision(revid)
 
613
                tree = self.tree_cache.revision_tree(revid)
 
614
                for path, obj, ie in self._revision_to_objects(rev, tree,
 
615
                    roundtrip=not lossy):
 
616
                    ret.append((obj, path))
 
617
        finally:
 
618
            pb.finished()
 
619
        return ret
 
620
 
 
621
    def add_thin_pack(self):
 
622
        import tempfile
 
623
        import os
 
624
        fd, path = tempfile.mkstemp(suffix=".pack")
 
625
        f = os.fdopen(fd, 'wb')
 
626
        def commit():
 
627
            from dulwich.pack import PackData, Pack
 
628
            from bzrlib.plugins.git.fetch import import_git_objects
 
629
            os.fsync(fd)
 
630
            f.close()
 
631
            if os.path.getsize(path) == 0:
 
632
                return
 
633
            pd = PackData(path)
 
634
            pd.create_index_v2(path[:-5]+".idx", self.object_store.get_raw)
 
635
 
 
636
            p = Pack(path[:-5])
 
637
            self.repository.lock_write()
 
638
            try:
 
639
                self.repository.start_write_group()
 
640
                try:
 
641
                    import_git_objects(self.repository, self.mapping, 
 
642
                        p.iterobjects(get_raw=self.get_raw),
 
643
                        self.object_store)
 
644
                except:
 
645
                    self.repository.abort_write_group()
 
646
                    raise
 
647
                else:
 
648
                    self.repository.commit_write_group()
 
649
            finally:
 
650
                self.repository.unlock()
 
651
        return f, commit
 
652
 
 
653
    # The pack isn't kept around anyway, so no point 
 
654
    # in treating full packs different from thin packs
 
655
    add_pack = add_thin_pack