/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

Use BZR_PLUGINS_AT.

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