/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 repository.py

merge versionedfiles support.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
# Copyright (C) 2007 Canonical Ltd
2
 
# Copyright (C) 2008-2009 Jelmer Vernooij <jelmer@samba.org>
3
2
#
4
3
# This program is free software; you can redistribute it and/or modify
5
4
# it under the terms of the GNU General Public License as published by
17
16
 
18
17
"""An adapter between a Git Repository and a Bazaar Branch"""
19
18
 
20
 
import dulwich as git
 
19
import git
21
20
import os
22
21
import time
23
22
 
24
23
import bzrlib
25
24
from bzrlib import (
 
25
    deprecated_graph,
26
26
    errors,
27
 
    graph,
28
27
    inventory,
29
28
    osutils,
30
29
    repository,
31
30
    revision,
32
31
    revisiontree,
33
 
    ui,
34
32
    urlutils,
 
33
    versionedfile,
35
34
    )
36
 
from bzrlib.foreign import (
37
 
        ForeignRepository,
38
 
        )
39
 
from bzrlib.trace import mutter
40
35
from bzrlib.transport import get_transport
41
36
 
42
 
from bzrlib.plugins.git.foreign import (
43
 
    versionedfiles,
44
 
    )
45
 
from bzrlib.plugins.git.mapping import (
46
 
    default_mapping,
47
 
    inventory_to_tree_and_blobs,
48
 
    mapping_registry,
49
 
    revision_to_commit,
50
 
    )
51
 
from bzrlib.plugins.git.versionedfiles import GitTexts
52
 
 
53
 
 
54
 
class GitTags(object):
55
 
 
56
 
    def __init__(self, tags):
57
 
        self._tags = tags
58
 
 
59
 
    def __iter__(self):
60
 
        return iter(self._tags)
61
 
 
62
 
 
63
 
class GitRepository(ForeignRepository):
 
37
from bzrlib.plugins.git import (
 
38
    versionedfiles
 
39
    )
 
40
from bzrlib.plugins.git.mapping import default_mapping
 
41
 
 
42
 
 
43
class GitRepository(repository.Repository):
64
44
    """An adapter to git repositories for bzr."""
65
45
 
66
46
    _serializer = None
67
47
 
68
48
    def __init__(self, gitdir, lockfiles):
69
 
        ForeignRepository.__init__(self, GitFormat(), gitdir, lockfiles)
70
 
        from bzrlib.plugins.git import fetch
71
 
        repository.InterRepository.register_optimiser(fetch.InterGitRepository)
72
 
        repository.InterRepository.register_optimiser(fetch.InterGitNonGitRepository)
73
 
 
74
 
    def is_shared(self):
75
 
        return True
76
 
 
77
 
    def supports_rich_root(self):
78
 
        return True
79
 
 
80
 
    def _warn_if_deprecated(self):
81
 
        # This class isn't deprecated
82
 
        pass
83
 
 
84
 
    def get_mapping(self):
85
 
        return default_mapping
86
 
 
87
 
    def make_working_trees(self):
88
 
        return True
89
 
 
90
 
 
91
 
class LocalGitRepository(GitRepository):
92
 
 
93
 
    def __init__(self, gitdir, lockfiles):
94
 
        # FIXME: This also caches negatives. Need to be more careful 
95
 
        # about this once we start writing to git
96
 
        self._parents_provider = graph.CachingParentsProvider(self)
97
 
        GitRepository.__init__(self, gitdir, lockfiles)
98
49
        self.base = gitdir.root_transport.base
 
50
        self.bzrdir = gitdir
 
51
        self.control_files = lockfiles
99
52
        self._git = gitdir._git
100
53
        self.texts = None
101
54
        self.signatures = versionedfiles.VirtualSignatureTexts(self)
102
55
        self.revisions = versionedfiles.VirtualRevisionTexts(self)
103
 
        self.inventories = versionedfiles.VirtualInventoryTexts(self)
104
 
        self.texts = GitTexts(self)
105
 
        self.tags = GitTags(self._git.get_tags())
 
56
        self._format = GitFormat()
 
57
        self._fallback_repositories = []
106
58
 
107
 
    def all_revision_ids(self):
108
 
        ret = set([revision.NULL_REVISION])
109
 
        heads = self._git.heads()
110
 
        if heads == {}:
111
 
            return ret
112
 
        bzr_heads = [self.get_mapping().revision_id_foreign_to_bzr(h) for h in heads.itervalues()]
113
 
        ret = set(bzr_heads)
114
 
        graph = self.get_graph()
115
 
        for rev, parents in graph.iter_ancestry(bzr_heads):
116
 
            ret.add(rev)
 
59
    def _all_revision_ids(self):
 
60
        if self._git.heads == []:
 
61
            return set()
 
62
        ret = set()
 
63
        skip = 0
 
64
        max_count = 1000
 
65
        cms = None
 
66
        while cms != []:
 
67
            cms = self._git.commits("--all", max_count=max_count, skip=skip)
 
68
            skip += max_count
 
69
            ret.update([default_mapping.convert_revision_id_git_to_bzr(cm.id) for cm in cms])
117
70
        return ret
118
71
 
 
72
    def is_shared(self):
 
73
        return True
 
74
 
 
75
    def supports_rich_root(self):
 
76
        return False
 
77
 
119
78
    #def get_revision_delta(self, revision_id):
120
79
    #    parent_revid = self.get_revision(revision_id).parent_ids[0]
121
80
    #    diff = self._git.diff(ids.convert_revision_id_bzr_to_git(parent_revid),
122
81
    #                   ids.convert_revision_id_bzr_to_git(revision_id))
123
82
 
124
 
    def _make_parents_provider(self):
125
 
        """See Repository._make_parents_provider()."""
126
 
        return self._parents_provider
127
 
 
128
 
    def get_parent_map(self, revids):
129
 
        parent_map = {}
130
 
        mutter("get_parent_map(%r)", revids)
131
 
        for revision_id in revids:
132
 
            assert isinstance(revision_id, str)
133
 
            if revision_id == revision.NULL_REVISION:
134
 
                parent_map[revision_id] = ()
135
 
                continue
136
 
            hexsha, mapping = self.lookup_git_revid(revision_id)
137
 
            commit  = self._git.commit(hexsha)
138
 
            if commit is None:
139
 
                continue
140
 
            else:
141
 
                parent_map[revision_id] = [mapping.revision_id_foreign_to_bzr(p) for p in commit.parents]
142
 
        return parent_map
143
 
 
144
 
    def get_ancestry(self, revision_id, topo_sorted=True):
145
 
        """See Repository.get_ancestry().
146
 
        """
147
 
        if revision_id is None:
148
 
            return [None, revision.NULL_REVISION] + self._all_revision_ids()
149
 
        assert isinstance(revision_id, str)
150
 
        ancestry = []
151
 
        graph = self.get_graph()
152
 
        for rev, parents in graph.iter_ancestry([revision_id]):
153
 
            ancestry.append(rev)
154
 
        ancestry.reverse()
155
 
        return [None] + ancestry
156
 
 
157
 
    def import_revision_gist(self, source, revid, parent_lookup):
158
 
        """Import the gist of a revision into this Git repository.
159
 
 
160
 
        """
161
 
        objects = []
162
 
        rev = source.get_revision(revid)
163
 
        for sha, object, path in inventory_to_tree_and_blobs(source, None, revid):
164
 
            if path == "":
165
 
                tree_sha = sha
166
 
            objects.append((object, path))
167
 
        commit = revision_to_commit(rev, tree_sha, parent_lookup)
168
 
        objects.append((commit, None))
169
 
        self._git.object_store.add_objects(objects)
170
 
        return commit.sha().hexdigest()
171
 
 
172
 
    def dfetch(self, source, stop_revision):
173
 
        """Import the gist of the ancestry of a particular revision."""
174
 
        if stop_revision is None:
175
 
            raise NotImplementedError
176
 
        revidmap = {}
177
 
        gitidmap = {}
178
 
        todo = []
179
 
        source.lock_write()
180
 
        try:
181
 
            graph = source.get_graph()
182
 
            ancestry = [x for x in source.get_ancestry(stop_revision) if x is not None]
183
 
            for revid in graph.iter_topo_order(ancestry):
184
 
                if not self.has_revision(revid):
185
 
                    todo.append(revid)
186
 
            pb = ui.ui_factory.nested_progress_bar()
187
 
            try:
188
 
                for i, revid in enumerate(todo):
189
 
                    pb.update("pushing revisions", i, len(todo))
190
 
                    git_commit = self.import_revision_gist(source, revid, gitidmap.__getitem__)
191
 
                    gitidmap[revid] = git_commit
192
 
                    git_revid = self.get_mapping().revision_id_foreign_to_bzr(git_commit)
193
 
                    revidmap[revid] = git_revid
194
 
            finally:
195
 
                pb.finished()
196
 
            source.fetch(self, revision_id=revidmap[stop_revision])
197
 
        finally:
198
 
            source.unlock()
199
 
        return revidmap
 
83
    def get_ancestry(self, revision_id):
 
84
        revision_id = revision.ensure_null(revision_id)
 
85
        ret = []
 
86
        if revision_id != revision.NULL_REVISION:
 
87
            skip = 0
 
88
            max_count = 1000
 
89
            cms = None
 
90
            while cms != []:
 
91
                cms = self._git.commits(default_mapping.convert_revision_id_bzr_to_git(revision_id), max_count=max_count, skip=skip)
 
92
                skip += max_count
 
93
                ret += [default_mapping.convert_revision_id_git_to_bzr(cm.id) for cm in cms]
 
94
        return [None] + ret
200
95
 
201
96
    def get_signature_text(self, revision_id):
202
97
        raise errors.NoSuchRevision(self, revision_id)
203
98
 
204
 
    def lookup_revision_id(self, revid):
205
 
        """Lookup a revision id.
206
 
        
207
 
        :param revid: Bazaar revision id.
208
 
        :return: Tuple with git revisionid and mapping.
209
 
        """
210
 
        # Yes, this doesn't really work, but good enough as a stub
211
 
        return osutils.sha(rev_id).hexdigest(), self.get_mapping()
212
 
 
213
99
    def has_signature_for_revision_id(self, revision_id):
214
100
        return False
215
101
 
216
 
    def lookup_git_revid(self, bzr_revid):
217
 
        try:
218
 
            return mapping_registry.revision_id_bzr_to_foreign(bzr_revid)
219
 
        except errors.InvalidRevisionId:
220
 
            raise errors.NoSuchRevision(self, bzr_revid)
 
102
    def get_parent_map(self, revision_ids):
 
103
        ret = {}
 
104
        for revid in revision_ids:
 
105
            if revid == revision.NULL_REVISION:
 
106
                ret[revid] = ()
 
107
            else:
 
108
                commit = self._git.commit(default_mapping.convert_revision_id_bzr_to_git(revid))
 
109
                ret[revid] = tuple([default_mapping.convert_revision_id_git_to_bzr(p.id) for p in commit.parents])
 
110
        return ret
221
111
 
222
112
    def get_revision(self, revision_id):
223
 
        git_commit_id, mapping = self.lookup_git_revid(revision_id)
224
 
        try:
225
 
            commit = self._git.commit(git_commit_id)
226
 
        except KeyError:
227
 
            raise errors.NoSuchRevision(self, revision_id)
 
113
        git_commit_id = default_mapping.convert_revision_id_bzr_to_git(revision_id)
 
114
        commit = self._git.commit(git_commit_id)
228
115
        # print "fetched revision:", git_commit_id
229
 
        revision = mapping.import_commit(commit)
230
 
        assert revision is not None
 
116
        revision = self._parse_rev(commit)
231
117
        return revision
232
118
 
233
119
    def has_revision(self, revision_id):
234
120
        try:
235
121
            self.get_revision(revision_id)
236
 
        except errors.NoSuchRevision:
 
122
        except NoSuchRevision:
237
123
            return False
238
124
        else:
239
125
            return True
240
126
 
241
 
    def get_revisions(self, revids):
242
 
        return [self.get_revision(r) for r in revids]
 
127
    def get_revisions(self, revisions):
 
128
        return [self.get_revision(r) for r in revisions]
 
129
 
 
130
    @classmethod
 
131
    def _parse_rev(klass, commit):
 
132
        """Convert a git commit to a bzr revision.
 
133
 
 
134
        :return: a `bzrlib.revision.Revision` object.
 
135
        """
 
136
        rev = revision.Revision(default_mapping.convert_revision_id_git_to_bzr(commit.id))
 
137
        rev.parent_ids = tuple([default_mapping.convert_revision_id_git_to_bzr(p.id) for p in commit.parents])
 
138
        rev.inventory_sha1 = ""
 
139
        rev.message = commit.message.decode("utf-8", "replace")
 
140
        rev.committer = str(commit.committer)
 
141
        rev.properties['author'] = str(commit.author)
 
142
        rev.timestamp = time.mktime(commit.committed_date)
 
143
        rev.timezone = 0
 
144
        return rev
243
145
 
244
146
    def revision_trees(self, revids):
245
147
        for revid in revids:
259
161
        assert revision_id != None
260
162
        return self.revision_tree(revision_id).inventory
261
163
 
262
 
    def set_make_working_trees(self, trees):
263
 
        pass
264
 
 
265
 
    def fetch_objects(self, determine_wants, graph_walker, resolve_ext_ref, progress=None):
266
 
        return self._git.fetch_objects(determine_wants, graph_walker, progress)
 
164
 
 
165
def escape_file_id(file_id):
 
166
    return file_id.replace('_', '__').replace(' ', '_s')
 
167
 
 
168
 
 
169
def unescape_file_id(file_id):
 
170
    return file_id.replace("_s", " ").replace("__", "_")
267
171
 
268
172
 
269
173
class GitRevisionTree(revisiontree.RevisionTree):
271
175
    def __init__(self, repository, revision_id):
272
176
        self._repository = repository
273
177
        self.revision_id = revision_id
274
 
        assert isinstance(revision_id, str)
275
 
        git_id, self.mapping = repository.lookup_git_revid(revision_id)
276
 
        try:
277
 
            commit = repository._git.commit(git_id)
278
 
        except KeyError, r:
279
 
            raise errors.NoSuchRevision(repository, revision_id)
280
 
        self.tree = commit.tree
 
178
        git_id = default_mapping.convert_revision_id_bzr_to_git(revision_id)
 
179
        self.tree = repository._git.commit(git_id).tree
281
180
        self._inventory = inventory.Inventory(revision_id=revision_id)
282
181
        self._inventory.root.revision = revision_id
283
182
        self._build_inventory(self.tree, self._inventory.root, "")
288
187
    def get_file_text(self, file_id):
289
188
        entry = self._inventory[file_id]
290
189
        if entry.kind == 'directory': return ""
291
 
        return self._repository._git.get_blob(entry.text_id).data
 
190
        return self._repository._git.blob(entry.text_id).data
292
191
 
293
 
    def _build_inventory(self, tree_id, ie, path):
 
192
    def _build_inventory(self, tree, ie, path):
294
193
        assert isinstance(path, str)
295
 
        tree = self._repository._git.tree(tree_id)
296
 
        for mode, name, hexsha in tree.entries():
297
 
            basename = name.decode("utf-8")
 
194
        for b in tree.contents:
 
195
            basename = b.name.decode("utf-8")
298
196
            if path == "":
299
 
                child_path = name
 
197
                child_path = b.name
300
198
            else:
301
 
                child_path = urlutils.join(path, name)
302
 
            file_id = self.mapping.generate_file_id(child_path)
303
 
            entry_kind = (mode & 0700000) / 0100000
304
 
            if entry_kind == 0:
 
199
                child_path = urlutils.join(path, b.name)
 
200
            file_id = escape_file_id(child_path.encode('utf-8'))
 
201
            if b.mode[0] == '0':
305
202
                child_ie = inventory.InventoryDirectory(file_id, basename, ie.file_id)
306
 
            elif entry_kind == 1:
307
 
                file_kind = (mode & 070000) / 010000
308
 
                b = self._repository._git.get_blob(hexsha)
309
 
                if file_kind == 0:
 
203
            elif b.mode[0] == '1':
 
204
                if b.mode[1] == '0':
310
205
                    child_ie = inventory.InventoryFile(file_id, basename, ie.file_id)
311
206
                    child_ie.text_sha1 = osutils.sha_string(b.data)
312
 
                elif file_kind == 2:
 
207
                elif b.mode[1] == '2':
313
208
                    child_ie = inventory.InventoryLink(file_id, basename, ie.file_id)
314
209
                    child_ie.text_sha1 = osutils.sha_string("")
315
210
                else:
316
211
                    raise AssertionError(
317
 
                        "Unknown file kind, perms=%o." % (mode,))
 
212
                        "Unknown file kind, perms=%r." % (b.mode,))
318
213
                child_ie.text_id = b.id
319
 
                child_ie.text_size = len(b.data)
 
214
                child_ie.text_size = b.size
320
215
            else:
321
216
                raise AssertionError(
322
 
                    "Unknown blob kind, perms=%r." % (mode,))
323
 
            fs_mode = mode & 0777
324
 
            child_ie.executable = bool(fs_mode & 0111)
 
217
                    "Unknown blob kind, perms=%r." % (b.mode,))
 
218
            child_ie.executable = bool(int(b.mode[3:], 8) & 0111)
325
219
            child_ie.revision = self.revision_id
326
220
            self._inventory.add(child_ie)
327
 
            if entry_kind == 0:
328
 
                self._build_inventory(hexsha, child_ie, child_path)
 
221
            if b.mode[0] == '0':
 
222
                self._build_inventory(b, child_ie, child_path)
329
223
 
330
224
 
331
225
class GitFormat(object):
332
226
 
333
227
    supports_tree_reference = False
334
 
    rich_root_data = True
335
228
 
336
229
    def get_format_description(self):
337
230
        return "Git Repository"
338
 
 
339
 
    def initialize(self, url, shared=False, _internal=False):
340
 
        raise bzr_errors.UninitializableFormat(self)
341
 
 
342
 
    def check_conversion_target(self, target_repo_format):
343
 
        return target_repo_format.rich_root_data