/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

  • Committer: John Arbash Meinel
  • Date: 2010-01-12 22:51:31 UTC
  • mto: This revision was merged to the branch mainline in revision 4955.
  • Revision ID: john@arbash-meinel.com-20100112225131-he8h411p6aeeb947
Delay grabbing an output stream until we actually go to show a diff.

This makes the test suite happy, but it also seems to be reasonable.
If we aren't going to write anything, we don't need to hold an
output stream open.

Show diffs side-by-side

added added

removed removed

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