Add cgit compatibility testing framework.
[jelmer/dulwich-libgit2.git] / dulwich / tests / test_repository.py
1 # test_repository.py -- tests for repository.py
2 # Copyright (C) 2007 James Westby <jw+debian@jameswestby.net>
3
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; version 2
7 # of the License or (at your option) any later version of 
8 # the License.
9
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 # GNU General Public License for more details.
14
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
18 # MA  02110-1301, USA.
19
20
21 """Tests for the repository."""
22
23 from cStringIO import StringIO
24 import os
25 import shutil
26 import tempfile
27 import unittest
28
29 from dulwich import errors
30 from dulwich.repo import (
31     check_ref_format,
32     Repo,
33     read_packed_refs,
34     read_packed_refs_with_peeled,
35     write_packed_refs,
36     _split_ref_line,
37     )
38 from dulwich.tests.utils import (
39     open_repo,
40     tear_down_repo,
41     )
42
43 missing_sha = 'b91fa4d900e17e99b433218e988c4eb4a3e9a097'
44
45
46 class CreateRepositoryTests(unittest.TestCase):
47
48     def test_create(self):
49         tmp_dir = tempfile.mkdtemp()
50         try:
51             repo = Repo.init_bare(tmp_dir)
52             self.assertEquals(tmp_dir, repo._controldir)
53         finally:
54             shutil.rmtree(tmp_dir)
55
56
57 class RepositoryTests(unittest.TestCase):
58
59     def setUp(self):
60         self._repo = None
61
62     def tearDown(self):
63         if self._repo is not None:
64             tear_down_repo(self._repo)
65   
66     def test_simple_props(self):
67         r = self._repo = open_repo('a.git')
68         self.assertEqual(r.controldir(), r.path)
69   
70     def test_ref(self):
71         r = self._repo = open_repo('a.git')
72         self.assertEqual(r.ref('refs/heads/master'),
73                          'a90fa2d900a17e99b433217e988c4eb4a2e9a097')
74   
75     def test_get_refs(self):
76         r = self._repo = open_repo('a.git')
77         self.assertEqual({
78             'HEAD': 'a90fa2d900a17e99b433217e988c4eb4a2e9a097', 
79             'refs/heads/master': 'a90fa2d900a17e99b433217e988c4eb4a2e9a097'
80             }, r.get_refs())
81   
82     def test_head(self):
83         r = self._repo = open_repo('a.git')
84         self.assertEqual(r.head(), 'a90fa2d900a17e99b433217e988c4eb4a2e9a097')
85   
86     def test_get_object(self):
87         r = self._repo = open_repo('a.git')
88         obj = r.get_object(r.head())
89         self.assertEqual(obj._type, 'commit')
90   
91     def test_get_object_non_existant(self):
92         r = self._repo = open_repo('a.git')
93         self.assertRaises(KeyError, r.get_object, missing_sha)
94   
95     def test_commit(self):
96         r = self._repo = open_repo('a.git')
97         obj = r.commit(r.head())
98         self.assertEqual(obj._type, 'commit')
99   
100     def test_commit_not_commit(self):
101         r = self._repo = open_repo('a.git')
102         self.assertRaises(errors.NotCommitError,
103                           r.commit, '4f2e6529203aa6d44b5af6e3292c837ceda003f9')
104   
105     def test_tree(self):
106         r = self._repo = open_repo('a.git')
107         commit = r.commit(r.head())
108         tree = r.tree(commit.tree)
109         self.assertEqual(tree._type, 'tree')
110         self.assertEqual(tree.sha().hexdigest(), commit.tree)
111   
112     def test_tree_not_tree(self):
113         r = self._repo = open_repo('a.git')
114         self.assertRaises(errors.NotTreeError, r.tree, r.head())
115   
116     def test_get_blob(self):
117         r = self._repo = open_repo('a.git')
118         commit = r.commit(r.head())
119         tree = r.tree(commit.tree)
120         blob_sha = tree.entries()[0][2]
121         blob = r.get_blob(blob_sha)
122         self.assertEqual(blob._type, 'blob')
123         self.assertEqual(blob.sha().hexdigest(), blob_sha)
124   
125     def test_get_blob_notblob(self):
126         r = self._repo = open_repo('a.git')
127         self.assertRaises(errors.NotBlobError, r.get_blob, r.head())
128     
129     def test_linear_history(self):
130         r = self._repo = open_repo('a.git')
131         history = r.revision_history(r.head())
132         shas = [c.sha().hexdigest() for c in history]
133         self.assertEqual(shas, [r.head(),
134                                 '2a72d929692c41d8554c07f6301757ba18a65d91'])
135   
136     def test_merge_history(self):
137         r = self._repo = open_repo('simple_merge.git')
138         history = r.revision_history(r.head())
139         shas = [c.sha().hexdigest() for c in history]
140         self.assertEqual(shas, ['5dac377bdded4c9aeb8dff595f0faeebcc8498cc',
141                                 'ab64bbdcc51b170d21588e5c5d391ee5c0c96dfd',
142                                 '4cffe90e0a41ad3f5190079d7c8f036bde29cbe6',
143                                 '60dacdc733de308bb77bb76ce0fb0f9b44c9769e',
144                                 '0d89f20333fbb1d2f3a94da77f4981373d8f4310'])
145   
146     def test_revision_history_missing_commit(self):
147         r = self._repo = open_repo('simple_merge.git')
148         self.assertRaises(errors.MissingCommitError, r.revision_history,
149                           missing_sha)
150   
151     def test_out_of_order_merge(self):
152         """Test that revision history is ordered by date, not parent order."""
153         r = self._repo = open_repo('ooo_merge.git')
154         history = r.revision_history(r.head())
155         shas = [c.sha().hexdigest() for c in history]
156         self.assertEqual(shas, ['7601d7f6231db6a57f7bbb79ee52e4d462fd44d1',
157                                 'f507291b64138b875c28e03469025b1ea20bc614',
158                                 'fb5b0425c7ce46959bec94d54b9a157645e114f5',
159                                 'f9e39b120c68182a4ba35349f832d0e4e61f485c'])
160   
161     def test_get_tags_empty(self):
162         r = self._repo = open_repo('ooo_merge.git')
163         self.assertEqual({}, r.refs.as_dict('refs/tags'))
164
165     def test_get_config(self):
166         r = self._repo = open_repo('ooo_merge.git')
167         self.assertEquals({}, r.get_config())
168
169
170 class CheckRefFormatTests(unittest.TestCase):
171     """Tests for the check_ref_format function.
172
173     These are the same tests as in the git test suite.
174     """
175
176     def test_valid(self):
177         self.assertTrue(check_ref_format('heads/foo'))
178         self.assertTrue(check_ref_format('foo/bar/baz'))
179         self.assertTrue(check_ref_format('refs///heads/foo'))
180         self.assertTrue(check_ref_format('foo./bar'))
181         self.assertTrue(check_ref_format('heads/foo@bar'))
182         self.assertTrue(check_ref_format('heads/fix.lock.error'))
183
184     def test_invalid(self):
185         self.assertFalse(check_ref_format('foo'))
186         self.assertFalse(check_ref_format('heads/foo/'))
187         self.assertFalse(check_ref_format('./foo'))
188         self.assertFalse(check_ref_format('.refs/foo'))
189         self.assertFalse(check_ref_format('heads/foo..bar'))
190         self.assertFalse(check_ref_format('heads/foo?bar'))
191         self.assertFalse(check_ref_format('heads/foo.lock'))
192         self.assertFalse(check_ref_format('heads/v@{ation'))
193         self.assertFalse(check_ref_format('heads/foo\bar'))
194
195
196 ONES = "1" * 40
197 TWOS = "2" * 40
198 THREES = "3" * 40
199 FOURS = "4" * 40
200
201 class PackedRefsFileTests(unittest.TestCase):
202     def test_split_ref_line_errors(self):
203         self.assertRaises(errors.PackedRefsException, _split_ref_line,
204                           'singlefield')
205         self.assertRaises(errors.PackedRefsException, _split_ref_line,
206                           'badsha name')
207         self.assertRaises(errors.PackedRefsException, _split_ref_line,
208                           '%s bad/../refname' % ONES)
209
210     def test_read_without_peeled(self):
211         f = StringIO('# comment\n%s ref/1\n%s ref/2' % (ONES, TWOS))
212         self.assertEqual([(ONES, 'ref/1'), (TWOS, 'ref/2')],
213                          list(read_packed_refs(f)))
214
215     def test_read_without_peeled_errors(self):
216         f = StringIO('%s ref/1\n^%s' % (ONES, TWOS))
217         self.assertRaises(errors.PackedRefsException, list, read_packed_refs(f))
218
219     def test_read_with_peeled(self):
220         f = StringIO('%s ref/1\n%s ref/2\n^%s\n%s ref/4' % (
221             ONES, TWOS, THREES, FOURS))
222         self.assertEqual([
223             (ONES, 'ref/1', None),
224             (TWOS, 'ref/2', THREES),
225             (FOURS, 'ref/4', None),
226             ], list(read_packed_refs_with_peeled(f)))
227
228     def test_read_with_peeled_errors(self):
229         f = StringIO('^%s\n%s ref/1' % (TWOS, ONES))
230         self.assertRaises(errors.PackedRefsException, list, read_packed_refs(f))
231
232         f = StringIO('%s ref/1\n^%s\n^%s' % (ONES, TWOS, THREES))
233         self.assertRaises(errors.PackedRefsException, list, read_packed_refs(f))
234
235     def test_write_with_peeled(self):
236         f = StringIO()
237         write_packed_refs(f, {'ref/1': ONES, 'ref/2': TWOS},
238                           {'ref/1': THREES})
239         self.assertEqual(
240             "# pack-refs with: peeled\n%s ref/1\n^%s\n%s ref/2\n" % (
241             ONES, THREES, TWOS), f.getvalue())
242
243     def test_write_without_peeled(self):
244         f = StringIO()
245         write_packed_refs(f, {'ref/1': ONES, 'ref/2': TWOS})
246         self.assertEqual("%s ref/1\n%s ref/2\n" % (ONES, TWOS), f.getvalue())
247
248
249 class RefsContainerTests(unittest.TestCase):
250     def setUp(self):
251         self._repo = open_repo('refs.git')
252         self._refs = self._repo.refs
253
254     def tearDown(self):
255         tear_down_repo(self._repo)
256
257     def test_get_packed_refs(self):
258         self.assertEqual(
259             {'refs/tags/refs-0.1': 'df6800012397fb85c56e7418dd4eb9405dee075c'},
260             self._refs.get_packed_refs())
261
262     def test_keys(self):
263         self.assertEqual([
264             'HEAD',
265             'refs/heads/loop',
266             'refs/heads/master',
267             'refs/tags/refs-0.1',
268             ], sorted(list(self._refs.keys())))
269         self.assertEqual(['loop', 'master'],
270                          sorted(self._refs.keys('refs/heads')))
271         self.assertEqual(['refs-0.1'], list(self._refs.keys('refs/tags')))
272
273     def test_as_dict(self):
274         # refs/heads/loop does not show up
275         self.assertEqual({
276             'HEAD': '42d06bd4b77fed026b154d16493e5deab78f02ec',
277             'refs/heads/master': '42d06bd4b77fed026b154d16493e5deab78f02ec',
278             'refs/tags/refs-0.1': 'df6800012397fb85c56e7418dd4eb9405dee075c',
279             }, self._refs.as_dict())
280
281     def test_setitem(self):
282         self._refs['refs/some/ref'] = '42d06bd4b77fed026b154d16493e5deab78f02ec'
283         self.assertEqual('42d06bd4b77fed026b154d16493e5deab78f02ec',
284                          self._refs['refs/some/ref'])
285         f = open(os.path.join(self._refs.path, 'refs', 'some', 'ref'), 'rb')
286         self.assertEqual('42d06bd4b77fed026b154d16493e5deab78f02ec',
287                           f.read()[:40])
288         f.close()
289
290     def test_setitem_symbolic(self):
291         ones = '1' * 40
292         self._refs['HEAD'] = ones
293         self.assertEqual(ones, self._refs['HEAD'])
294
295         # ensure HEAD was not modified
296         f = open(os.path.join(self._refs.path, 'HEAD'), 'rb')
297         self.assertEqual('ref: refs/heads/master', iter(f).next().rstrip('\n'))
298         f.close()
299
300         # ensure the symbolic link was written through
301         f = open(os.path.join(self._refs.path, 'refs', 'heads', 'master'), 'rb')
302         self.assertEqual(ones, f.read()[:40])
303         f.close()
304
305     def test_set_if_equals(self):
306         nines = '9' * 40
307         self.assertFalse(self._refs.set_if_equals('HEAD', 'c0ffee', nines))
308         self.assertEqual('42d06bd4b77fed026b154d16493e5deab78f02ec',
309                          self._refs['HEAD'])
310
311         self.assertTrue(self._refs.set_if_equals(
312             'HEAD', '42d06bd4b77fed026b154d16493e5deab78f02ec', nines))
313         self.assertEqual(nines, self._refs['HEAD'])
314
315         # ensure symref was followed
316         self.assertEqual(nines, self._refs['refs/heads/master'])
317
318         self.assertFalse(os.path.exists(
319             os.path.join(self._refs.path, 'refs', 'heads', 'master.lock')))
320         self.assertFalse(os.path.exists(
321             os.path.join(self._refs.path, 'HEAD.lock')))
322
323     def test_add_if_new(self):
324         nines = '9' * 40
325         self.assertFalse(self._refs.add_if_new('refs/heads/master', nines))
326         self.assertEqual('42d06bd4b77fed026b154d16493e5deab78f02ec',
327                          self._refs['refs/heads/master'])
328
329         self.assertTrue(self._refs.add_if_new('refs/some/ref', nines))
330         self.assertEqual(nines, self._refs['refs/some/ref'])
331
332         # don't overwrite packed ref
333         self.assertFalse(self._refs.add_if_new('refs/tags/refs-0.1', nines))
334         self.assertEqual('df6800012397fb85c56e7418dd4eb9405dee075c',
335                          self._refs['refs/tags/refs-0.1'])
336
337     def test_check_refname(self):
338         try:
339             self._refs._check_refname('HEAD')
340         except KeyError:
341             self.fail()
342
343         try:
344             self._refs._check_refname('refs/heads/foo')
345         except KeyError:
346             self.fail()
347
348         self.assertRaises(KeyError, self._refs._check_refname, 'refs')
349         self.assertRaises(KeyError, self._refs._check_refname, 'notrefs/foo')
350
351     def test_follow(self):
352         self.assertEquals(
353             ('refs/heads/master', '42d06bd4b77fed026b154d16493e5deab78f02ec'),
354             self._refs._follow('HEAD'))
355         self.assertEquals(
356             ('refs/heads/master', '42d06bd4b77fed026b154d16493e5deab78f02ec'),
357             self._refs._follow('refs/heads/master'))
358         self.assertRaises(KeyError, self._refs._follow, 'notrefs/foo')
359         self.assertRaises(KeyError, self._refs._follow, 'refs/heads/loop')
360
361     def test_delitem(self):
362         self.assertEqual('42d06bd4b77fed026b154d16493e5deab78f02ec',
363                           self._refs['refs/heads/master'])
364         del self._refs['refs/heads/master']
365         self.assertRaises(KeyError, lambda: self._refs['refs/heads/master'])
366         ref_file = os.path.join(self._refs.path, 'refs', 'heads', 'master')
367         self.assertFalse(os.path.exists(ref_file))
368         self.assertFalse('refs/heads/master' in self._refs.get_packed_refs())
369
370     def test_delitem_symbolic(self):
371         self.assertEqual('ref: refs/heads/master',
372                           self._refs.read_loose_ref('HEAD'))
373         del self._refs['HEAD']
374         self.assertRaises(KeyError, lambda: self._refs['HEAD'])
375         self.assertEqual('42d06bd4b77fed026b154d16493e5deab78f02ec',
376                          self._refs['refs/heads/master'])
377         self.assertFalse(os.path.exists(os.path.join(self._refs.path, 'HEAD')))
378
379     def test_remove_if_equals(self):
380         nines = '9' * 40
381         self.assertFalse(self._refs.remove_if_equals('HEAD', 'c0ffee'))
382         self.assertEqual('42d06bd4b77fed026b154d16493e5deab78f02ec',
383                          self._refs['HEAD'])
384
385         # HEAD is a symref, so shouldn't equal its dereferenced value
386         self.assertFalse(self._refs.remove_if_equals(
387             'HEAD', '42d06bd4b77fed026b154d16493e5deab78f02ec'))
388         self.assertTrue(self._refs.remove_if_equals(
389             'refs/heads/master', '42d06bd4b77fed026b154d16493e5deab78f02ec'))
390         self.assertRaises(KeyError, lambda: self._refs['refs/heads/master'])
391
392         # HEAD is now a broken symref
393         self.assertRaises(KeyError, lambda: self._refs['HEAD'])
394         self.assertEqual('ref: refs/heads/master',
395                           self._refs.read_loose_ref('HEAD'))
396
397         self.assertFalse(os.path.exists(
398             os.path.join(self._refs.path, 'refs', 'heads', 'master.lock')))
399         self.assertFalse(os.path.exists(
400             os.path.join(self._refs.path, 'HEAD.lock')))
401
402         # test removing ref that is only packed
403         self.assertEqual('df6800012397fb85c56e7418dd4eb9405dee075c',
404                          self._refs['refs/tags/refs-0.1'])
405         self.assertTrue(
406             self._refs.remove_if_equals('refs/tags/refs-0.1',
407             'df6800012397fb85c56e7418dd4eb9405dee075c'))
408         self.assertRaises(KeyError, lambda: self._refs['refs/tags/refs-0.1'])