/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 test for init-repo.

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
        def parent_lookup(revid):
 
181
            try:
 
182
                return gitidmap[revid]
 
183
            except KeyError:
 
184
                return self.lookup_git_revid(revid)[0]
 
185
        todo = []
 
186
        source.lock_write()
 
187
        try:
 
188
            graph = source.get_graph()
 
189
            ancestry = [x for x in source.get_ancestry(stop_revision) if x is not None]
 
190
            for revid in graph.iter_topo_order(ancestry):
 
191
                if not self.has_revision(revid):
 
192
                    todo.append(revid)
 
193
            pb = ui.ui_factory.nested_progress_bar()
 
194
            try:
 
195
                for i, revid in enumerate(todo):
 
196
                    pb.update("pushing revisions", i, len(todo))
 
197
                    git_commit = self.import_revision_gist(source, revid,
 
198
                        parent_lookup)
 
199
                    gitidmap[revid] = git_commit
 
200
                    git_revid = self.get_mapping().revision_id_foreign_to_bzr(
 
201
                        git_commit)
 
202
                    revidmap[revid] = git_revid
 
203
            finally:
 
204
                pb.finished()
 
205
            source.fetch(self, revision_id=revidmap[stop_revision])
 
206
        finally:
 
207
            source.unlock()
 
208
        return revidmap
106
209
 
107
210
    def get_signature_text(self, revision_id):
108
211
        raise errors.NoSuchRevision(self, revision_id)
109
212
 
 
213
    def lookup_revision_id(self, revid):
 
214
        """Lookup a revision id.
 
215
        
 
216
        :param revid: Bazaar revision id.
 
217
        :return: Tuple with git revisionid and mapping.
 
218
        """
 
219
        # Yes, this doesn't really work, but good enough as a stub
 
220
        return osutils.sha(rev_id).hexdigest(), self.get_mapping()
 
221
 
110
222
    def has_signature_for_revision_id(self, revision_id):
111
223
        return False
112
224
 
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
 
225
    def lookup_git_revid(self, bzr_revid):
 
226
        try:
 
227
            return mapping_registry.revision_id_bzr_to_foreign(bzr_revid)
 
228
        except errors.InvalidRevisionId:
 
229
            raise errors.NoSuchRevision(self, bzr_revid)
119
230
 
120
231
    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)
 
232
        git_commit_id, mapping = self.lookup_git_revid(revision_id)
 
233
        try:
 
234
            commit = self._git.commit(git_commit_id)
 
235
        except KeyError:
 
236
            raise errors.NoSuchRevision(self, revision_id)
123
237
        # print "fetched revision:", git_commit_id
124
 
        revision = self._parse_rev(commit)
 
238
        revision = mapping.import_commit(commit)
 
239
        assert revision is not None
125
240
        return revision
126
241
 
127
242
    def has_revision(self, revision_id):
128
243
        try:
129
244
            self.get_revision(revision_id)
130
 
        except NoSuchRevision:
 
245
        except errors.NoSuchRevision:
131
246
            return False
132
247
        else:
133
248
            return True
134
249
 
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
 
250
    def get_revisions(self, revids):
 
251
        return [self.get_revision(r) for r in revids]
153
252
 
154
253
    def revision_trees(self, revids):
155
254
        for revid in revids:
157
256
 
158
257
    def revision_tree(self, revision_id):
159
258
        revision_id = revision.ensure_null(revision_id)
160
 
 
161
259
        if revision_id == revision.NULL_REVISION:
162
260
            inv = inventory.Inventory(root_id=None)
163
261
            inv.revision_id = revision_id
164
262
            return revisiontree.RevisionTree(self, inv, revision_id)
165
 
 
166
263
        return GitRevisionTree(self, revision_id)
167
264
 
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
265
    def get_inventory(self, revision_id):
200
266
        assert revision_id != None
201
267
        return self.revision_tree(revision_id).inventory
202
268
 
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')
 
269
    def set_make_working_trees(self, trees):
 
270
        pass
 
271
 
 
272
    def fetch_objects(self, determine_wants, graph_walker, resolve_ext_ref,
 
273
        progress=None):
 
274
        return self._git.fetch_objects(determine_wants, graph_walker, progress)
279
275
 
280
276
 
281
277
class GitRevisionTree(revisiontree.RevisionTree):
283
279
    def __init__(self, repository, revision_id):
284
280
        self._repository = repository
285
281
        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
 
282
        assert isinstance(revision_id, str)
 
283
        git_id, self.mapping = repository.lookup_git_revid(revision_id)
 
284
        try:
 
285
            commit = repository._git.commit(git_id)
 
286
        except KeyError, r:
 
287
            raise errors.NoSuchRevision(repository, revision_id)
 
288
        self.tree = commit.tree
288
289
        self._inventory = inventory.Inventory(revision_id=revision_id)
289
290
        self._inventory.root.revision = revision_id
290
291
        self._build_inventory(self.tree, self._inventory.root, "")
291
292
 
292
 
    def get_file_lines(self, file_id):
 
293
    def get_revision_id(self):
 
294
        return self.revision_id
 
295
 
 
296
    def get_file_text(self, file_id):
293
297
        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)
 
298
        if entry.kind == 'directory': return ""
 
299
        return self._repository._git.get_blob(entry.text_id).data
299
300
 
300
 
    def _build_inventory(self, tree, ie, path):
 
301
    def _build_inventory(self, tree_id, ie, path):
301
302
        assert isinstance(path, str)
302
 
        for b in tree.contents:
303
 
            basename = b.name.decode("utf-8")
 
303
        tree = self._repository._git.tree(tree_id)
 
304
        for mode, name, hexsha in tree.entries():
 
305
            basename = name.decode("utf-8")
304
306
            if path == "":
305
 
                child_path = b.name
 
307
                child_path = name
306
308
            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':
 
309
                child_path = urlutils.join(path, name)
 
310
            file_id = self.mapping.generate_file_id(child_path)
 
311
            entry_kind = (mode & 0700000) / 0100000
 
312
            if entry_kind == 0:
310
313
                child_ie = inventory.InventoryDirectory(file_id, basename, ie.file_id)
311
 
            elif b.mode[0] == '1':
312
 
                if b.mode[1] == '0':
 
314
            elif entry_kind == 1:
 
315
                file_kind = (mode & 070000) / 010000
 
316
                b = self._repository._git.get_blob(hexsha)
 
317
                if file_kind == 0:
313
318
                    child_ie = inventory.InventoryFile(file_id, basename, ie.file_id)
314
319
                    child_ie.text_sha1 = osutils.sha_string(b.data)
315
 
                elif b.mode[1] == '2':
 
320
                elif file_kind == 2:
316
321
                    child_ie = inventory.InventoryLink(file_id, basename, ie.file_id)
317
322
                    child_ie.text_sha1 = osutils.sha_string("")
318
323
                else:
319
324
                    raise AssertionError(
320
 
                        "Unknown file kind, perms=%r." % (b.mode,))
321
 
                child_ie.text_size = b.size
 
325
                        "Unknown file kind, perms=%o." % (mode,))
 
326
                child_ie.text_id = b.id
 
327
                child_ie.text_size = len(b.data)
322
328
            else:
323
329
                raise AssertionError(
324
 
                    "Unknown blob kind, perms=%r." % (b.mode,))
325
 
            child_ie.executable = bool(int(b.mode[3:], 8) & 0111)
 
330
                    "Unknown blob kind, perms=%r." % (mode,))
 
331
            fs_mode = mode & 0777
 
332
            child_ie.executable = bool(fs_mode & 0111)
326
333
            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)
 
334
            self._inventory.add(child_ie)
 
335
            if entry_kind == 0:
 
336
                self._build_inventory(hexsha, child_ie, child_path)
331
337
 
332
338
 
333
339
class GitFormat(object):
334
340
 
335
341
    supports_tree_reference = False
 
342
    rich_root_data = True
336
343
 
337
344
    def get_format_description(self):
338
345
        return "Git Repository"
 
346
 
 
347
    def initialize(self, url, shared=False, _internal=False):
 
348
        raise bzr_errors.UninitializableFormat(self)
 
349
 
 
350
    def check_conversion_target(self, target_repo_format):
 
351
        return target_repo_format.rich_root_data