/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

Handle empty metadata.

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