/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

Default to HEAD rather than refs/heads/master again.

Show diffs side-by-side

added added

removed removed

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