/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

Add simple HACKING document.

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
        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)
52
98
        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()
 
99
        self._git = gitdir._git
65
100
        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
 
101
        self.signatures = versionedfiles.VirtualSignatureTexts(self)
 
102
        self.revisions = versionedfiles.VirtualRevisionTexts(self)
 
103
        self.inventories = versionedfiles.VirtualInventoryTexts(self)
 
104
        self.texts = GitTexts(self)
 
105
        self.tags = GitTags(self._git.get_tags())
 
106
 
 
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)
 
117
        return ret
 
118
 
 
119
    #def get_revision_delta(self, revision_id):
 
120
    #    parent_revid = self.get_revision(revision_id).parent_ids[0]
 
121
    #    diff = self._git.diff(ids.convert_revision_id_bzr_to_git(parent_revid),
 
122
    #                   ids.convert_revision_id_bzr_to_git(revision_id))
 
123
 
 
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
106
200
 
107
201
    def get_signature_text(self, revision_id):
108
202
        raise errors.NoSuchRevision(self, revision_id)
109
203
 
 
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
 
110
213
    def has_signature_for_revision_id(self, revision_id):
111
214
        return False
112
215
 
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
 
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)
119
221
 
120
222
    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)
 
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)
123
228
        # print "fetched revision:", git_commit_id
124
 
        revision = self._parse_rev(commit)
 
229
        revision = mapping.import_commit(commit)
 
230
        assert revision is not None
125
231
        return revision
126
232
 
127
233
    def has_revision(self, revision_id):
128
234
        try:
129
235
            self.get_revision(revision_id)
130
 
        except NoSuchRevision:
 
236
        except errors.NoSuchRevision:
131
237
            return False
132
238
        else:
133
239
            return True
134
240
 
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
 
241
    def get_revisions(self, revids):
 
242
        return [self.get_revision(r) for r in revids]
153
243
 
154
244
    def revision_trees(self, revids):
155
245
        for revid in revids:
165
255
 
166
256
        return GitRevisionTree(self, revision_id)
167
257
 
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
258
    def get_inventory(self, revision_id):
200
259
        assert revision_id != None
201
260
        return self.revision_tree(revision_id).inventory
202
261
 
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')
 
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)
279
267
 
280
268
 
281
269
class GitRevisionTree(revisiontree.RevisionTree):
283
271
    def __init__(self, repository, revision_id):
284
272
        self._repository = repository
285
273
        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
 
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
288
281
        self._inventory = inventory.Inventory(revision_id=revision_id)
289
282
        self._inventory.root.revision = revision_id
290
283
        self._build_inventory(self.tree, self._inventory.root, "")
291
284
 
292
 
    def get_file_lines(self, file_id):
 
285
    def get_revision_id(self):
 
286
        return self.revision_id
 
287
 
 
288
    def get_file_text(self, file_id):
293
289
        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)
 
290
        if entry.kind == 'directory': return ""
 
291
        return self._repository._git.get_blob(entry.text_id).data
299
292
 
300
 
    def _build_inventory(self, tree, ie, path):
 
293
    def _build_inventory(self, tree_id, ie, path):
301
294
        assert isinstance(path, str)
302
 
        for b in tree.contents:
303
 
            basename = b.name.decode("utf-8")
 
295
        tree = self._repository._git.tree(tree_id)
 
296
        for mode, name, hexsha in tree.entries():
 
297
            basename = name.decode("utf-8")
304
298
            if path == "":
305
 
                child_path = b.name
 
299
                child_path = name
306
300
            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':
 
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:
310
305
                child_ie = inventory.InventoryDirectory(file_id, basename, ie.file_id)
311
 
            elif b.mode[0] == '1':
312
 
                if b.mode[1] == '0':
 
306
            elif entry_kind == 1:
 
307
                file_kind = (mode & 070000) / 010000
 
308
                b = self._repository._git.get_blob(hexsha)
 
309
                if file_kind == 0:
313
310
                    child_ie = inventory.InventoryFile(file_id, basename, ie.file_id)
314
311
                    child_ie.text_sha1 = osutils.sha_string(b.data)
315
 
                elif b.mode[1] == '2':
 
312
                elif file_kind == 2:
316
313
                    child_ie = inventory.InventoryLink(file_id, basename, ie.file_id)
317
314
                    child_ie.text_sha1 = osutils.sha_string("")
318
315
                else:
319
316
                    raise AssertionError(
320
 
                        "Unknown file kind, perms=%r." % (b.mode,))
321
 
                child_ie.text_size = b.size
 
317
                        "Unknown file kind, perms=%o." % (mode,))
 
318
                child_ie.text_id = b.id
 
319
                child_ie.text_size = len(b.data)
322
320
            else:
323
321
                raise AssertionError(
324
 
                    "Unknown blob kind, perms=%r." % (b.mode,))
325
 
            child_ie.executable = bool(int(b.mode[3:], 8) & 0111)
 
322
                    "Unknown blob kind, perms=%r." % (mode,))
 
323
            fs_mode = mode & 0777
 
324
            child_ie.executable = bool(fs_mode & 0111)
326
325
            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)
 
326
            self._inventory.add(child_ie)
 
327
            if entry_kind == 0:
 
328
                self._build_inventory(hexsha, child_ie, child_path)
331
329
 
332
330
 
333
331
class GitFormat(object):
334
332
 
335
333
    supports_tree_reference = False
 
334
    rich_root_data = True
336
335
 
337
336
    def get_format_description(self):
338
337
        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