/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

Support bzr.dev.

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