/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

Fix typo.

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