1# Copyright (C) 2007 Canonical Ltd
2# Copyright (C) 2007-2018 Jelmer Vernooij <jelmer@jelmer.uk>
3#
4# This program is free software; you can redistribute it and/or modify
5# it under the terms of the GNU General Public License as published by
6# the Free Software Foundation; either version 2 of the License, or
7# (at your option) any later version.
8#
9# This program is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12# GNU General Public License for more details.
13#
14# You should have received a copy of the GNU General Public License
15# along with this program; if not, write to the Free Software
16# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
18"""Tests for interfacing with a Git Repository"""
19
20import dulwich
21from dulwich.repo import (
22    Repo as GitRepo,
23    )
24import os
25
26from ... import (
27    config,
28    errors,
29    revision,
30    )
31from ...repository import (
32    InterRepository,
33    Repository,
34    )
35
36from .. import (
37    dir,
38    repository,
39    tests,
40    )
41from ..mapping import (
42    default_mapping,
43    )
44from ..object_store import (
45    BazaarObjectStore,
46    )
47from ..push import (
48    MissingObjectsIterator,
49    )
50
51
52class TestGitRepositoryFeatures(tests.TestCaseInTempDir):
53    """Feature tests for GitRepository."""
54
55    def _do_commit(self):
56        builder = tests.GitBranchBuilder()
57        builder.set_file(b'a', b'text for a\n', False)
58        commit_handle = builder.commit(b'Joe Foo <joe@foo.com>', b'message')
59        mapping = builder.finish()
60        return mapping[commit_handle]
61
62    def test_open_existing(self):
63        GitRepo.init(self.test_dir)
64
65        repo = Repository.open('.')
66        self.assertIsInstance(repo, repository.GitRepository)
67
68    def test_has_git_repo(self):
69        GitRepo.init(self.test_dir)
70
71        repo = Repository.open('.')
72        self.assertIsInstance(repo._git, dulwich.repo.BaseRepo)
73
74    def test_has_revision(self):
75        GitRepo.init(self.test_dir)
76        commit_id = self._do_commit()
77        repo = Repository.open('.')
78        self.assertFalse(repo.has_revision(b'foobar'))
79        revid = default_mapping.revision_id_foreign_to_bzr(commit_id)
80        self.assertTrue(repo.has_revision(revid))
81
82    def test_has_revisions(self):
83        GitRepo.init(self.test_dir)
84        commit_id = self._do_commit()
85        repo = Repository.open('.')
86        self.assertEqual(set(), repo.has_revisions([b'foobar']))
87        revid = default_mapping.revision_id_foreign_to_bzr(commit_id)
88        self.assertEqual(set([revid]), repo.has_revisions([b'foobar', revid]))
89
90    def test_get_revision(self):
91        # GitRepository.get_revision gives a Revision object.
92
93        # Create a git repository with a revision.
94        GitRepo.init(self.test_dir)
95        commit_id = self._do_commit()
96
97        # Get the corresponding Revision object.
98        revid = default_mapping.revision_id_foreign_to_bzr(commit_id)
99        repo = Repository.open('.')
100        rev = repo.get_revision(revid)
101        self.assertIsInstance(rev, revision.Revision)
102
103    def test_get_revision_unknown(self):
104        GitRepo.init(self.test_dir)
105
106        repo = Repository.open('.')
107        self.assertRaises(errors.NoSuchRevision, repo.get_revision, b"bla")
108
109    def simple_commit(self):
110        # Create a git repository with some interesting files in a revision.
111        GitRepo.init(self.test_dir)
112        builder = tests.GitBranchBuilder()
113        builder.set_file(b'data', b'text\n', False)
114        builder.set_file(b'executable', b'content', True)
115        builder.set_symlink(b'link', b'broken')
116        builder.set_file(b'subdir/subfile', b'subdir text\n', False)
117        commit_handle = builder.commit(b'Joe Foo <joe@foo.com>', b'message',
118                                       timestamp=1205433193)
119        mapping = builder.finish()
120        return mapping[commit_handle]
121
122    def test_pack(self):
123        commit_id = self.simple_commit()
124        repo = Repository.open('.')
125        repo.pack()
126
127    def test_revision_tree(self):
128        commit_id = self.simple_commit()
129        revid = default_mapping.revision_id_foreign_to_bzr(commit_id)
130        repo = Repository.open('.')
131        tree = repo.revision_tree(revid)
132        self.assertEqual(tree.get_revision_id(), revid)
133        self.assertEqual(b"text\n", tree.get_file_text("data"))
134
135
136class TestGitRepository(tests.TestCaseWithTransport):
137
138    def _do_commit(self):
139        builder = tests.GitBranchBuilder()
140        builder.set_file(b'a', b'text for a\n', False)
141        commit_handle = builder.commit(b'Joe Foo <joe@foo.com>', b'message')
142        mapping = builder.finish()
143        return mapping[commit_handle]
144
145    def setUp(self):
146        tests.TestCaseWithTransport.setUp(self)
147        dulwich.repo.Repo.create(self.test_dir)
148        self.git_repo = Repository.open(self.test_dir)
149
150    def test_supports_rich_root(self):
151        repo = self.git_repo
152        self.assertEqual(repo.supports_rich_root(), True)
153
154    def test_get_signature_text(self):
155        self.assertRaises(
156            errors.NoSuchRevision, self.git_repo.get_signature_text, revision.NULL_REVISION)
157
158    def test_has_signature_for_revision_id(self):
159        self.assertEqual(False, self.git_repo.has_signature_for_revision_id(
160            revision.NULL_REVISION))
161
162    def test_all_revision_ids_none(self):
163        self.assertEqual([], self.git_repo.all_revision_ids())
164
165    def test_get_known_graph_ancestry(self):
166        cid = self._do_commit()
167        revid = default_mapping.revision_id_foreign_to_bzr(cid)
168        g = self.git_repo.get_known_graph_ancestry([revid])
169        self.assertEqual(frozenset([revid]),
170                         g.heads([revid]))
171        self.assertEqual([(revid, 0, (1,), True)],
172                         [(n.key, n.merge_depth, n.revno, n.end_of_merge)
173                          for n in g.merge_sort(revid)])
174
175    def test_all_revision_ids(self):
176        commit_id = self._do_commit()
177        self.assertEqual(
178            [default_mapping.revision_id_foreign_to_bzr(commit_id)],
179            self.git_repo.all_revision_ids())
180
181    def assertIsNullInventory(self, inv):
182        self.assertEqual(inv.root, None)
183        self.assertEqual(inv.revision_id, revision.NULL_REVISION)
184        self.assertEqual(list(inv.iter_entries()), [])
185
186    def test_revision_tree_none(self):
187        # GitRepository.revision_tree(None) returns the null tree.
188        repo = self.git_repo
189        tree = repo.revision_tree(revision.NULL_REVISION)
190        self.assertEqual(tree.get_revision_id(), revision.NULL_REVISION)
191        self.assertIs(None, tree.path2id(''))
192
193    def test_get_parent_map_null(self):
194        self.assertEqual({revision.NULL_REVISION: ()},
195                         self.git_repo.get_parent_map([revision.NULL_REVISION]))
196
197
198class SigningGitRepository(tests.TestCaseWithTransport):
199
200    def test_signed_commit(self):
201        import breezy.gpg
202        oldstrategy = breezy.gpg.GPGStrategy
203        wt = self.make_branch_and_tree('.', format='git')
204        branch = wt.branch
205        revid = wt.commit("base", allow_pointless=True)
206        self.assertFalse(
207            branch.repository.has_signature_for_revision_id(revid))
208        try:
209            breezy.gpg.GPGStrategy = breezy.gpg.LoopbackGPGStrategy
210            conf = config.MemoryStack(b'''
211create_signatures=always
212''')
213            revid2 = wt.commit(config=conf, message="base",
214                               allow_pointless=True)
215
216            def sign(text):
217                return breezy.gpg.LoopbackGPGStrategy(None).sign(text)
218            self.assertIsInstance(
219                branch.repository.get_signature_text(revid2), bytes)
220        finally:
221            breezy.gpg.GPGStrategy = oldstrategy
222
223
224class RevpropsRepository(tests.TestCaseWithTransport):
225
226    def test_author(self):
227        wt = self.make_branch_and_tree('.', format='git')
228        revid = wt.commit(
229            "base", allow_pointless=True,
230            revprops={'author': 'Joe Example <joe@example.com>'})
231        rev = wt.branch.repository.get_revision(revid)
232        r = dulwich.repo.Repo('.')
233        self.assertEqual(b'Joe Example <joe@example.com>', r[r.head()].author)
234
235    def test_authors(self):
236        wt = self.make_branch_and_tree('.', format='git')
237        revid = wt.commit(
238            "base", allow_pointless=True,
239            revprops={'authors': 'Joe Example <joe@example.com>'})
240        rev = wt.branch.repository.get_revision(revid)
241        r = dulwich.repo.Repo('.')
242        self.assertEqual(b'Joe Example <joe@example.com>', r[r.head()].author)
243
244    def test_multiple_authors(self):
245        wt = self.make_branch_and_tree('.', format='git')
246        self.assertRaises(
247            Exception, wt.commit, "base", allow_pointless=True,
248            revprops={'authors': 'Joe Example <joe@example.com>\n'
249                                 'Jane Doe <jane@example.com\n>'})
250
251    def test_bugs(self):
252        wt = self.make_branch_and_tree('.', format='git')
253        revid = wt.commit(
254            "base", allow_pointless=True,
255            revprops={
256                'bugs': 'https://github.com/jelmer/dulwich/issues/123 fixed\n'
257                })
258        rev = wt.branch.repository.get_revision(revid)
259        r = dulwich.repo.Repo('.')
260        self.assertEqual(
261            b'base\n'
262            b'Fixes: https://github.com/jelmer/dulwich/issues/123\n',
263            r[r.head()].message)
264
265
266class GitRepositoryFormat(tests.TestCase):
267
268    def setUp(self):
269        super(GitRepositoryFormat, self).setUp()
270        self.format = repository.GitRepositoryFormat()
271
272    def test_get_format_description(self):
273        self.assertEqual("Git Repository",
274                         self.format.get_format_description())
275
276
277class RevisionGistImportTests(tests.TestCaseWithTransport):
278
279    def setUp(self):
280        tests.TestCaseWithTransport.setUp(self)
281        self.git_path = os.path.join(self.test_dir, "git")
282        os.mkdir(self.git_path)
283        dulwich.repo.Repo.create(self.git_path)
284        self.git_repo = Repository.open(self.git_path)
285        self.bzr_tree = self.make_branch_and_tree("bzr")
286
287    def get_inter(self):
288        return InterRepository.get(self.bzr_tree.branch.repository,
289                                   self.git_repo)
290
291    def object_iter(self):
292        store = BazaarObjectStore(
293            self.bzr_tree.branch.repository, default_mapping)
294        store_iterator = MissingObjectsIterator(
295            store, self.bzr_tree.branch.repository)
296        return store, store_iterator
297
298    def import_rev(self, revid, parent_lookup=None):
299        store, store_iter = self.object_iter()
300        store._cache.idmap.start_write_group()
301        try:
302            return store_iter.import_revision(revid, lossy=True)
303        except:
304            store._cache.idmap.abort_write_group()
305            raise
306        else:
307            store._cache.idmap.commit_write_group()
308
309    def test_pointless(self):
310        revid = self.bzr_tree.commit("pointless", timestamp=1205433193,
311                                     timezone=0, committer="Jelmer Vernooij <jelmer@samba.org>")
312        self.assertEqual(b"2caa8094a5b794961cd9bf582e3e2bb090db0b14",
313                         self.import_rev(revid))
314        self.assertEqual(b"2caa8094a5b794961cd9bf582e3e2bb090db0b14",
315                         self.import_rev(revid))
316
317
318class ForeignTestsRepositoryFactory(object):
319
320    def make_repository(self, transport):
321        return dir.LocalGitControlDirFormat().initialize_on_transport(transport).open_repository()
322