Merge Dave's fixes for the compatibility tests and web.
[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 import warnings
29
30 from dulwich import errors
31 from dulwich import objects
32 from dulwich.repo import (
33     check_ref_format,
34     Repo,
35     read_packed_refs,
36     read_packed_refs_with_peeled,
37     write_packed_refs,
38     _split_ref_line,
39     )
40 from dulwich.tests.utils import (
41     open_repo,
42     tear_down_repo,
43     )
44
45 missing_sha = 'b91fa4d900e17e99b433218e988c4eb4a3e9a097'
46
47
48 class CreateRepositoryTests(unittest.TestCase):
49
50     def test_create(self):
51         tmp_dir = tempfile.mkdtemp()
52         try:
53             repo = Repo.init_bare(tmp_dir)
54             self.assertEquals(tmp_dir, repo._controldir)
55         finally:
56             shutil.rmtree(tmp_dir)
57
58
59 class RepositoryTests(unittest.TestCase):
60
61     def setUp(self):
62         self._repo = None
63
64     def tearDown(self):
65         if self._repo is not None:
66             tear_down_repo(self._repo)
67   
68     def test_simple_props(self):
69         r = self._repo = open_repo('a.git')
70         self.assertEqual(r.controldir(), r.path)
71   
72     def test_ref(self):
73         r = self._repo = open_repo('a.git')
74         self.assertEqual(r.ref('refs/heads/master'),
75                          'a90fa2d900a17e99b433217e988c4eb4a2e9a097')
76
77     def test_setitem(self):
78         r = self._repo = open_repo('a.git')
79         r["refs/tags/foo"] = 'a90fa2d900a17e99b433217e988c4eb4a2e9a097'
80         self.assertEquals('a90fa2d900a17e99b433217e988c4eb4a2e9a097',
81                           r["refs/tags/foo"].id)
82   
83     def test_get_refs(self):
84         r = self._repo = open_repo('a.git')
85         self.assertEqual({
86             'HEAD': 'a90fa2d900a17e99b433217e988c4eb4a2e9a097',
87             'refs/heads/master': 'a90fa2d900a17e99b433217e988c4eb4a2e9a097',
88             'refs/tags/mytag': '28237f4dc30d0d462658d6b937b08a0f0b6ef55a',
89             'refs/tags/mytag-packed': 'b0931cadc54336e78a1d980420e3268903b57a50',
90             }, r.get_refs())
91   
92     def test_head(self):
93         r = self._repo = open_repo('a.git')
94         self.assertEqual(r.head(), 'a90fa2d900a17e99b433217e988c4eb4a2e9a097')
95
96     def test_get_object(self):
97         r = self._repo = open_repo('a.git')
98         obj = r.get_object(r.head())
99         self.assertEqual(obj.type_name, 'commit')
100
101     def test_get_object_non_existant(self):
102         r = self._repo = open_repo('a.git')
103         self.assertRaises(KeyError, r.get_object, missing_sha)
104
105     def test_commit(self):
106         r = self._repo = open_repo('a.git')
107         warnings.simplefilter("ignore", DeprecationWarning)
108         try:
109             obj = r.commit(r.head())
110         finally:
111             warnings.resetwarnings()
112         self.assertEqual(obj.type_name, 'commit')
113
114     def test_commit_not_commit(self):
115         r = self._repo = open_repo('a.git')
116         warnings.simplefilter("ignore", DeprecationWarning)
117         try:
118             self.assertRaises(errors.NotCommitError,
119                 r.commit, '4f2e6529203aa6d44b5af6e3292c837ceda003f9')
120         finally:
121             warnings.resetwarnings()
122
123     def test_tree(self):
124         r = self._repo = open_repo('a.git')
125         commit = r[r.head()]
126         warnings.simplefilter("ignore", DeprecationWarning)
127         try:
128             tree = r.tree(commit.tree)
129         finally:
130             warnings.resetwarnings()
131         self.assertEqual(tree.type_name, 'tree')
132         self.assertEqual(tree.sha().hexdigest(), commit.tree)
133
134     def test_tree_not_tree(self):
135         r = self._repo = open_repo('a.git')
136         warnings.simplefilter("ignore", DeprecationWarning)
137         try:
138             self.assertRaises(errors.NotTreeError, r.tree, r.head())
139         finally:
140             warnings.resetwarnings()
141
142     def test_tag(self):
143         r = self._repo = open_repo('a.git')
144         tag_sha = '28237f4dc30d0d462658d6b937b08a0f0b6ef55a'
145         warnings.simplefilter("ignore", DeprecationWarning)
146         try:
147             tag = r.tag(tag_sha)
148         finally:
149             warnings.resetwarnings()
150         self.assertEqual(tag.type_name, 'tag')
151         self.assertEqual(tag.sha().hexdigest(), tag_sha)
152         obj_class, obj_sha = tag.object
153         self.assertEqual(obj_class, objects.Commit)
154         self.assertEqual(obj_sha, r.head())
155
156     def test_tag_not_tag(self):
157         r = self._repo = open_repo('a.git')
158         warnings.simplefilter("ignore", DeprecationWarning)
159         try:
160             self.assertRaises(errors.NotTagError, r.tag, r.head())
161         finally:
162             warnings.resetwarnings()
163
164     def test_get_peeled(self):
165         # unpacked ref
166         r = self._repo = open_repo('a.git')
167         tag_sha = '28237f4dc30d0d462658d6b937b08a0f0b6ef55a'
168         self.assertNotEqual(r[tag_sha].sha().hexdigest(), r.head())
169         self.assertEqual(r.get_peeled('refs/tags/mytag'), r.head())
170
171         # packed ref with cached peeled value
172         packed_tag_sha = 'b0931cadc54336e78a1d980420e3268903b57a50'
173         parent_sha = r[r.head()].parents[0]
174         self.assertNotEqual(r[packed_tag_sha].sha().hexdigest(), parent_sha)
175         self.assertEqual(r.get_peeled('refs/tags/mytag-packed'), parent_sha)
176
177         # TODO: add more corner cases to test repo
178
179     def test_get_peeled_not_tag(self):
180         r = self._repo = open_repo('a.git')
181         self.assertEqual(r.get_peeled('HEAD'), r.head())
182
183     def test_get_blob(self):
184         r = self._repo = open_repo('a.git')
185         commit = r[r.head()]
186         tree = r[commit.tree]
187         blob_sha = tree.entries()[0][2]
188         warnings.simplefilter("ignore", DeprecationWarning)
189         try:
190             blob = r.get_blob(blob_sha)
191         finally:
192             warnings.resetwarnings()
193         self.assertEqual(blob.type_name, 'blob')
194         self.assertEqual(blob.sha().hexdigest(), blob_sha)
195
196     def test_get_blob_notblob(self):
197         r = self._repo = open_repo('a.git')
198         warnings.simplefilter("ignore", DeprecationWarning)
199         try:
200             self.assertRaises(errors.NotBlobError, r.get_blob, r.head())
201         finally:
202             warnings.resetwarnings()
203     
204     def test_linear_history(self):
205         r = self._repo = open_repo('a.git')
206         history = r.revision_history(r.head())
207         shas = [c.sha().hexdigest() for c in history]
208         self.assertEqual(shas, [r.head(),
209                                 '2a72d929692c41d8554c07f6301757ba18a65d91'])
210   
211     def test_merge_history(self):
212         r = self._repo = open_repo('simple_merge.git')
213         history = r.revision_history(r.head())
214         shas = [c.sha().hexdigest() for c in history]
215         self.assertEqual(shas, ['5dac377bdded4c9aeb8dff595f0faeebcc8498cc',
216                                 'ab64bbdcc51b170d21588e5c5d391ee5c0c96dfd',
217                                 '4cffe90e0a41ad3f5190079d7c8f036bde29cbe6',
218                                 '60dacdc733de308bb77bb76ce0fb0f9b44c9769e',
219                                 '0d89f20333fbb1d2f3a94da77f4981373d8f4310'])
220   
221     def test_revision_history_missing_commit(self):
222         r = self._repo = open_repo('simple_merge.git')
223         self.assertRaises(errors.MissingCommitError, r.revision_history,
224                           missing_sha)
225   
226     def test_out_of_order_merge(self):
227         """Test that revision history is ordered by date, not parent order."""
228         r = self._repo = open_repo('ooo_merge.git')
229         history = r.revision_history(r.head())
230         shas = [c.sha().hexdigest() for c in history]
231         self.assertEqual(shas, ['7601d7f6231db6a57f7bbb79ee52e4d462fd44d1',
232                                 'f507291b64138b875c28e03469025b1ea20bc614',
233                                 'fb5b0425c7ce46959bec94d54b9a157645e114f5',
234                                 'f9e39b120c68182a4ba35349f832d0e4e61f485c'])
235   
236     def test_get_tags_empty(self):
237         r = self._repo = open_repo('ooo_merge.git')
238         self.assertEqual({}, r.refs.as_dict('refs/tags'))
239
240     def test_get_config(self):
241         r = self._repo = open_repo('ooo_merge.git')
242         self.assertEquals({}, r.get_config())
243
244     def test_common_revisions(self):
245         """
246         This test demonstrates that ``find_common_revisions()`` actually returns
247         common heads, not revisions; dulwich already uses
248         ``find_common_revisions()`` in such a manner (see
249         ``Repo.fetch_objects()``).
250         """
251
252         expected_shas = set(['60dacdc733de308bb77bb76ce0fb0f9b44c9769e'])
253
254         # Source for objects.
255         r_base = open_repo('simple_merge.git')
256
257         # Re-create each-side of the merge in simple_merge.git.
258         #
259         # Since the trees and blobs are missing, the repository created is
260         # corrupted, but we're only checking for commits for the purpose of this
261         # test, so it's immaterial.
262         r1_dir = tempfile.mkdtemp()
263         r1_commits = ['ab64bbdcc51b170d21588e5c5d391ee5c0c96dfd', # HEAD
264                       '60dacdc733de308bb77bb76ce0fb0f9b44c9769e',
265                       '0d89f20333fbb1d2f3a94da77f4981373d8f4310']
266
267         r2_dir = tempfile.mkdtemp()
268         r2_commits = ['4cffe90e0a41ad3f5190079d7c8f036bde29cbe6', # HEAD
269                       '60dacdc733de308bb77bb76ce0fb0f9b44c9769e',
270                       '0d89f20333fbb1d2f3a94da77f4981373d8f4310']
271
272         try:
273             r1 = Repo.init_bare(r1_dir)
274             map(lambda c: r1.object_store.add_object(r_base.get_object(c)), \
275                 r1_commits)
276             r1.refs['HEAD'] = r1_commits[0]
277
278             r2 = Repo.init_bare(r2_dir)
279             map(lambda c: r2.object_store.add_object(r_base.get_object(c)), \
280                 r2_commits)
281             r2.refs['HEAD'] = r2_commits[0]
282
283             # Finally, the 'real' testing!
284             shas = r2.object_store.find_common_revisions(r1.get_graph_walker())
285             self.assertEqual(set(shas), expected_shas)
286
287             shas = r1.object_store.find_common_revisions(r2.get_graph_walker())
288             self.assertEqual(set(shas), expected_shas)
289         finally:
290             shutil.rmtree(r1_dir)
291             shutil.rmtree(r2_dir)
292
293
294 class CheckRefFormatTests(unittest.TestCase):
295     """Tests for the check_ref_format function.
296
297     These are the same tests as in the git test suite.
298     """
299
300     def test_valid(self):
301         self.assertTrue(check_ref_format('heads/foo'))
302         self.assertTrue(check_ref_format('foo/bar/baz'))
303         self.assertTrue(check_ref_format('refs///heads/foo'))
304         self.assertTrue(check_ref_format('foo./bar'))
305         self.assertTrue(check_ref_format('heads/foo@bar'))
306         self.assertTrue(check_ref_format('heads/fix.lock.error'))
307
308     def test_invalid(self):
309         self.assertFalse(check_ref_format('foo'))
310         self.assertFalse(check_ref_format('heads/foo/'))
311         self.assertFalse(check_ref_format('./foo'))
312         self.assertFalse(check_ref_format('.refs/foo'))
313         self.assertFalse(check_ref_format('heads/foo..bar'))
314         self.assertFalse(check_ref_format('heads/foo?bar'))
315         self.assertFalse(check_ref_format('heads/foo.lock'))
316         self.assertFalse(check_ref_format('heads/v@{ation'))
317         self.assertFalse(check_ref_format('heads/foo\bar'))
318
319
320 ONES = "1" * 40
321 TWOS = "2" * 40
322 THREES = "3" * 40
323 FOURS = "4" * 40
324
325 class PackedRefsFileTests(unittest.TestCase):
326
327     def test_split_ref_line_errors(self):
328         self.assertRaises(errors.PackedRefsException, _split_ref_line,
329                           'singlefield')
330         self.assertRaises(errors.PackedRefsException, _split_ref_line,
331                           'badsha name')
332         self.assertRaises(errors.PackedRefsException, _split_ref_line,
333                           '%s bad/../refname' % ONES)
334
335     def test_read_without_peeled(self):
336         f = StringIO('# comment\n%s ref/1\n%s ref/2' % (ONES, TWOS))
337         self.assertEqual([(ONES, 'ref/1'), (TWOS, 'ref/2')],
338                          list(read_packed_refs(f)))
339
340     def test_read_without_peeled_errors(self):
341         f = StringIO('%s ref/1\n^%s' % (ONES, TWOS))
342         self.assertRaises(errors.PackedRefsException, list, read_packed_refs(f))
343
344     def test_read_with_peeled(self):
345         f = StringIO('%s ref/1\n%s ref/2\n^%s\n%s ref/4' % (
346             ONES, TWOS, THREES, FOURS))
347         self.assertEqual([
348             (ONES, 'ref/1', None),
349             (TWOS, 'ref/2', THREES),
350             (FOURS, 'ref/4', None),
351             ], list(read_packed_refs_with_peeled(f)))
352
353     def test_read_with_peeled_errors(self):
354         f = StringIO('^%s\n%s ref/1' % (TWOS, ONES))
355         self.assertRaises(errors.PackedRefsException, list, read_packed_refs(f))
356
357         f = StringIO('%s ref/1\n^%s\n^%s' % (ONES, TWOS, THREES))
358         self.assertRaises(errors.PackedRefsException, list, read_packed_refs(f))
359
360     def test_write_with_peeled(self):
361         f = StringIO()
362         write_packed_refs(f, {'ref/1': ONES, 'ref/2': TWOS},
363                           {'ref/1': THREES})
364         self.assertEqual(
365             "# pack-refs with: peeled\n%s ref/1\n^%s\n%s ref/2\n" % (
366             ONES, THREES, TWOS), f.getvalue())
367
368     def test_write_without_peeled(self):
369         f = StringIO()
370         write_packed_refs(f, {'ref/1': ONES, 'ref/2': TWOS})
371         self.assertEqual("%s ref/1\n%s ref/2\n" % (ONES, TWOS), f.getvalue())
372
373
374 class RefsContainerTests(unittest.TestCase):
375
376     def setUp(self):
377         self._repo = open_repo('refs.git')
378         self._refs = self._repo.refs
379
380     def tearDown(self):
381         tear_down_repo(self._repo)
382
383     def test_get_packed_refs(self):
384         self.assertEqual({
385             'refs/heads/packed': '42d06bd4b77fed026b154d16493e5deab78f02ec',
386             'refs/tags/refs-0.1': 'df6800012397fb85c56e7418dd4eb9405dee075c',
387             }, self._refs.get_packed_refs())
388
389     def test_get_peeled_not_packed(self):
390         # not packed
391         self.assertEqual(None, self._refs.get_peeled('refs/tags/refs-0.2'))
392         self.assertEqual('3ec9c43c84ff242e3ef4a9fc5bc111fd780a76a8',
393                          self._refs['refs/tags/refs-0.2'])
394
395         # packed, known not peelable
396         self.assertEqual(self._refs['refs/heads/packed'],
397                          self._refs.get_peeled('refs/heads/packed'))
398
399         # packed, peeled
400         self.assertEqual('42d06bd4b77fed026b154d16493e5deab78f02ec',
401                          self._refs.get_peeled('refs/tags/refs-0.1'))
402
403     def test_keys(self):
404         self.assertEqual([
405             'HEAD',
406             'refs/heads/loop',
407             'refs/heads/master',
408             'refs/heads/packed',
409             'refs/tags/refs-0.1',
410             'refs/tags/refs-0.2',
411             ], sorted(list(self._refs.keys())))
412         self.assertEqual(['loop', 'master', 'packed'],
413                          sorted(self._refs.keys('refs/heads')))
414         self.assertEqual(['refs-0.1', 'refs-0.2'],
415                          sorted(self._refs.keys('refs/tags')))
416
417     def test_as_dict(self):
418         # refs/heads/loop does not show up
419         self.assertEqual({
420             'HEAD': '42d06bd4b77fed026b154d16493e5deab78f02ec',
421             'refs/heads/master': '42d06bd4b77fed026b154d16493e5deab78f02ec',
422             'refs/heads/packed': '42d06bd4b77fed026b154d16493e5deab78f02ec',
423             'refs/tags/refs-0.1': 'df6800012397fb85c56e7418dd4eb9405dee075c',
424             'refs/tags/refs-0.2': '3ec9c43c84ff242e3ef4a9fc5bc111fd780a76a8',
425             }, self._refs.as_dict())
426
427     def test_setitem(self):
428         self._refs['refs/some/ref'] = '42d06bd4b77fed026b154d16493e5deab78f02ec'
429         self.assertEqual('42d06bd4b77fed026b154d16493e5deab78f02ec',
430                          self._refs['refs/some/ref'])
431         f = open(os.path.join(self._refs.path, 'refs', 'some', 'ref'), 'rb')
432         self.assertEqual('42d06bd4b77fed026b154d16493e5deab78f02ec',
433                           f.read()[:40])
434         f.close()
435
436     def test_setitem_symbolic(self):
437         ones = '1' * 40
438         self._refs['HEAD'] = ones
439         self.assertEqual(ones, self._refs['HEAD'])
440
441         # ensure HEAD was not modified
442         f = open(os.path.join(self._refs.path, 'HEAD'), 'rb')
443         self.assertEqual('ref: refs/heads/master', iter(f).next().rstrip('\n'))
444         f.close()
445
446         # ensure the symbolic link was written through
447         f = open(os.path.join(self._refs.path, 'refs', 'heads', 'master'), 'rb')
448         self.assertEqual(ones, f.read()[:40])
449         f.close()
450
451     def test_set_if_equals(self):
452         nines = '9' * 40
453         self.assertFalse(self._refs.set_if_equals('HEAD', 'c0ffee', nines))
454         self.assertEqual('42d06bd4b77fed026b154d16493e5deab78f02ec',
455                          self._refs['HEAD'])
456
457         self.assertTrue(self._refs.set_if_equals(
458             'HEAD', '42d06bd4b77fed026b154d16493e5deab78f02ec', nines))
459         self.assertEqual(nines, self._refs['HEAD'])
460
461         # ensure symref was followed
462         self.assertEqual(nines, self._refs['refs/heads/master'])
463
464         self.assertFalse(os.path.exists(
465             os.path.join(self._refs.path, 'refs', 'heads', 'master.lock')))
466         self.assertFalse(os.path.exists(
467             os.path.join(self._refs.path, 'HEAD.lock')))
468
469     def test_add_if_new(self):
470         nines = '9' * 40
471         self.assertFalse(self._refs.add_if_new('refs/heads/master', nines))
472         self.assertEqual('42d06bd4b77fed026b154d16493e5deab78f02ec',
473                          self._refs['refs/heads/master'])
474
475         self.assertTrue(self._refs.add_if_new('refs/some/ref', nines))
476         self.assertEqual(nines, self._refs['refs/some/ref'])
477
478         # don't overwrite packed ref
479         self.assertFalse(self._refs.add_if_new('refs/tags/refs-0.1', nines))
480         self.assertEqual('df6800012397fb85c56e7418dd4eb9405dee075c',
481                          self._refs['refs/tags/refs-0.1'])
482
483     def test_check_refname(self):
484         try:
485             self._refs._check_refname('HEAD')
486         except KeyError:
487             self.fail()
488
489         try:
490             self._refs._check_refname('refs/heads/foo')
491         except KeyError:
492             self.fail()
493
494         self.assertRaises(KeyError, self._refs._check_refname, 'refs')
495         self.assertRaises(KeyError, self._refs._check_refname, 'notrefs/foo')
496
497     def test_follow(self):
498         self.assertEquals(
499             ('refs/heads/master', '42d06bd4b77fed026b154d16493e5deab78f02ec'),
500             self._refs._follow('HEAD'))
501         self.assertEquals(
502             ('refs/heads/master', '42d06bd4b77fed026b154d16493e5deab78f02ec'),
503             self._refs._follow('refs/heads/master'))
504         self.assertRaises(KeyError, self._refs._follow, 'notrefs/foo')
505         self.assertRaises(KeyError, self._refs._follow, 'refs/heads/loop')
506
507     def test_contains(self):
508         self.assertTrue('refs/heads/master' in self._refs)
509         self.assertFalse('refs/heads/bar' in self._refs)
510
511     def test_delitem(self):
512         self.assertEqual('42d06bd4b77fed026b154d16493e5deab78f02ec',
513                           self._refs['refs/heads/master'])
514         del self._refs['refs/heads/master']
515         self.assertRaises(KeyError, lambda: self._refs['refs/heads/master'])
516         ref_file = os.path.join(self._refs.path, 'refs', 'heads', 'master')
517         self.assertFalse(os.path.exists(ref_file))
518         self.assertFalse('refs/heads/master' in self._refs.get_packed_refs())
519
520     def test_delitem_symbolic(self):
521         self.assertEqual('ref: refs/heads/master',
522                           self._refs.read_loose_ref('HEAD'))
523         del self._refs['HEAD']
524         self.assertRaises(KeyError, lambda: self._refs['HEAD'])
525         self.assertEqual('42d06bd4b77fed026b154d16493e5deab78f02ec',
526                          self._refs['refs/heads/master'])
527         self.assertFalse(os.path.exists(os.path.join(self._refs.path, 'HEAD')))
528
529     def test_remove_if_equals(self):
530         nines = '9' * 40
531         self.assertFalse(self._refs.remove_if_equals('HEAD', 'c0ffee'))
532         self.assertEqual('42d06bd4b77fed026b154d16493e5deab78f02ec',
533                          self._refs['HEAD'])
534
535         # HEAD is a symref, so shouldn't equal its dereferenced value
536         self.assertFalse(self._refs.remove_if_equals(
537             'HEAD', '42d06bd4b77fed026b154d16493e5deab78f02ec'))
538         self.assertTrue(self._refs.remove_if_equals(
539             'refs/heads/master', '42d06bd4b77fed026b154d16493e5deab78f02ec'))
540         self.assertRaises(KeyError, lambda: self._refs['refs/heads/master'])
541
542         # HEAD is now a broken symref
543         self.assertRaises(KeyError, lambda: self._refs['HEAD'])
544         self.assertEqual('ref: refs/heads/master',
545                           self._refs.read_loose_ref('HEAD'))
546
547         self.assertFalse(os.path.exists(
548             os.path.join(self._refs.path, 'refs', 'heads', 'master.lock')))
549         self.assertFalse(os.path.exists(
550             os.path.join(self._refs.path, 'HEAD.lock')))
551
552         # test removing ref that is only packed
553         self.assertEqual('df6800012397fb85c56e7418dd4eb9405dee075c',
554                          self._refs['refs/tags/refs-0.1'])
555         self.assertTrue(
556             self._refs.remove_if_equals('refs/tags/refs-0.1',
557             'df6800012397fb85c56e7418dd4eb9405dee075c'))
558         self.assertRaises(KeyError, lambda: self._refs['refs/tags/refs-0.1'])
559
560     def test_read_ref(self):
561         self.assertEqual('ref: refs/heads/master', self._refs.read_ref("HEAD"))
562         self.assertEqual('42d06bd4b77fed026b154d16493e5deab78f02ec', 
563             self._refs.read_ref("refs/heads/packed"))
564         self.assertEqual(None,
565             self._refs.read_ref("nonexistant"))
566