summaryrefslogtreecommitdiff
path: root/morphlib
diff options
context:
space:
mode:
authorJannis Pohlmann <jannis.pohlmann@codethink.co.uk>2012-04-09 18:19:58 +0100
committerJannis Pohlmann <jannis.pohlmann@codethink.co.uk>2012-04-09 18:23:54 +0100
commit8311e0ceea0285f981251399ab793883420de01d (patch)
tree00bee9d2935fa400de574a6efbefa742946f8a9e /morphlib
parentaa47f79da62be1320c0b975edb55054b24448d7f (diff)
downloadmorph-8311e0ceea0285f981251399ab793883420de01d.tar.gz
Add CachedRepo class with tests.
Diffstat (limited to 'morphlib')
-rw-r--r--morphlib/__init__.py1
-rw-r--r--morphlib/cachedrepo.py147
-rw-r--r--morphlib/cachedrepo_tests.py181
3 files changed, 329 insertions, 0 deletions
diff --git a/morphlib/__init__.py b/morphlib/__init__.py
index a9281b85..62a4ab7c 100644
--- a/morphlib/__init__.py
+++ b/morphlib/__init__.py
@@ -25,6 +25,7 @@ import buildsystem
import buildworker
import builder
import cachedir
+import cachedrepo
import execute
import fsutils
import git
diff --git a/morphlib/cachedrepo.py b/morphlib/cachedrepo.py
new file mode 100644
index 00000000..998ba892
--- /dev/null
+++ b/morphlib/cachedrepo.py
@@ -0,0 +1,147 @@
+# Copyright (C) 2012 Codethink Limited
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; version 2 of the License.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+
+import binascii
+import cliapp
+import logging
+import os
+
+import morphlib.execute
+
+
+class InvalidReferenceError(cliapp.AppException):
+
+ def __init__(self, repo, ref):
+ Exception.__init__(self, '%s is an invalid reference for repo %s' %
+ (ref, repo))
+
+
+class UnresolvedNamedReferenceError(cliapp.AppException):
+
+ def __init__(self, repo, ref):
+ Exception.__init__(self, '%s is not a SHA1 ref for repo %s' %
+ (ref, repo))
+
+
+class CheckoutDirectoryExistsError(cliapp.AppException):
+
+ def __init__(self, repo, target_dir):
+ Exception.__init__(
+ self, 'Checkout directory %s for repo %s already exists' %
+ (target_dir, repo))
+
+
+class CheckoutError(cliapp.AppException):
+
+ def __init__(self, repo, ref, target_dir):
+ Exception.__init__(self, 'Failed to check out %s:%s into %s' %
+ (repo, ref, target_dir))
+
+
+class UpdateError(cliapp.AppException):
+
+ def __init__(self, repo):
+ Exception.__init__(self, 'Failed to update cached version of repo %s' %
+ repo)
+
+
+class CachedRepo(object):
+
+ def __init__(self, url, path):
+ self.url = url
+ self.path = path
+ self.ex = morphlib.execute.Execute(self.path, logging.debug)
+
+ def is_valid_sha1(self, ref):
+ valid_chars = 'abcdefABCDEF0123456789'
+ return len(ref) == 40 and all([x in valid_chars for x in ref])
+
+ def resolve_ref(self, ref):
+ try:
+ refs = self._show_ref(ref).split('\n')
+ # split each ref line into an array, drop non-origin branches
+ refs = [x.split() for x in refs if 'origin' in x]
+ binascii.unhexlify(refs[0][0])
+ return refs[0][0]
+ except morphlib.execute.CommandFailure:
+ if not self.is_valid_sha1(ref):
+ raise InvalidReferenceError(self, ref)
+ try:
+ binascii.unhexlify(ref)
+ return self._rev_list(ref)
+ except morphlib.execute.CommandFailure:
+ raise InvalidReferenceError(self, ref)
+
+ def cat(self, ref, filename):
+ if not self.is_valid_sha1(ref):
+ raise UnresolvedNamedReferenceError(self, ref)
+ try:
+ sha1 = self._rev_list(ref)
+ try:
+ return self._cat_file(sha1, filename)
+ except morphlib.execute.CommandFailure:
+ raise IOError('File %s does not exist in ref %s of repo %s' %
+ (filename, ref, self))
+ except morphlib.execute.CommandFailure:
+ raise InvalidReferenceError(self, ref)
+
+ def checkout(self, ref, target_dir):
+ if not self.is_valid_sha1(ref):
+ raise UnresolvedNamedReferenceError(self, ref)
+
+ if os.path.exists(target_dir):
+ raise CheckoutDirectoryExistsError(self, target_dir)
+
+ os.mkdir(target_dir)
+
+ try:
+ sha1 = self._rev_list(ref)
+ try:
+ self._copy_repository(self.path, target_dir)
+ self._checkout_ref(sha1, target_dir)
+ except morphlib.execute.CommandFailure:
+ raise CheckoutError(self, ref, target_dir)
+ except morphlib.execute.CommandFailure:
+ raise InvalidReferenceError(self, ref)
+
+ def update(self):
+ try:
+ self._update()
+ except morphlib.execute.CommandFailure:
+ raise UpdateError(self)
+
+ def _show_ref(self, ref): # pragma: no cover
+ return self.ex.runv(['git', 'show-ref', ref])
+
+ def _rev_list(self, ref): # pragma: no cover
+ return self.ex.runv(['git', 'rev-list', '--no-walk', ref])
+
+ def _cat_file(self, ref, filename): # pragma: no cover
+ return self.ex.runv(['git', 'cat-file', 'blob',
+ '%s:%s' % (ref, filename)])
+
+ def _copy_repository(self, source_dir, target_dir): # pragma: no cover
+ self.ex.runv(['cp', '-a', os.path.join(source_dir, '.git'),
+ target_dir])
+
+ def _checkout_ref(self, ref, target_dir): # pragma: no cover
+ self.ex.runv(['git', 'checkout', ref], pwd=target_dir)
+
+ def _update(self): # pragma: no cover
+ self.ex.runv(['git', 'remote', 'update', 'origin'])
+
+ def __str__(self): # pragma: no cover
+ return self.url
diff --git a/morphlib/cachedrepo_tests.py b/morphlib/cachedrepo_tests.py
new file mode 100644
index 00000000..79969a32
--- /dev/null
+++ b/morphlib/cachedrepo_tests.py
@@ -0,0 +1,181 @@
+# Copyright (C) 2012 Codethink Limited
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; version 2 of the License.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+
+import os
+import unittest
+
+import morphlib
+
+from morphlib import cachedrepo
+
+
+class CachedRepoTests(unittest.TestCase):
+
+ def show_ref(self, ref):
+ output = {
+ 'master':
+ 'e28a23812eadf2fce6583b8819b9c5dbd36b9fb9'
+ ' refs/remotes/origin/master',
+ 'baserock/morph':
+ '8b780e2e6f102fcf400ff973396566d36d730501'
+ ' refs/remotes/origin/baserock/morph',
+ }
+ try:
+ return output[ref]
+ except:
+ raise morphlib.execute.CommandFailure('git show-ref %s' % ref, '')
+
+ def rev_list(self, ref):
+ output = {
+ 'e28a23812eadf2fce6583b8819b9c5dbd36b9fb9':
+ 'e28a23812eadf2fce6583b8819b9c5dbd36b9fb9',
+ 'a4da32f5a81c8bc6d660404724cedc3bc0914a75':
+ 'a4da32f5a81c8bc6d660404724cedc3bc0914a75'
+ }
+ try:
+ return output[ref]
+ except:
+ raise morphlib.execute.CommandFailure('git rev-list %s' % ref, '')
+
+ def cat_file(self, ref, filename):
+ output = {
+ 'e28a23812eadf2fce6583b8819b9c5dbd36b9fb9:foo.morph':
+ 'contents of foo.morph'
+ }
+ try:
+ return output['%s:%s' % (ref, filename)]
+ except:
+ raise morphlib.execute.CommandFailure(
+ 'git cat-file blob %s:%s' % (ref, filename), '')
+
+ def copy_repository(self, source_dir, target_dir):
+ pass
+
+ def checkout_ref(self, ref, target_dir):
+ if ref == 'a4da32f5a81c8bc6d660404724cedc3bc0914a75':
+ # simulate a git failure or something similar to
+ # trigger a CheckoutError
+ raise morphlib.execute.CommandFailure('git checkout %s' % ref, '')
+ else:
+ with open(os.path.join(target_dir, 'foo.morph'), 'w') as f:
+ f.write('contents of foo.morph')
+
+ def update_successfully(self):
+ pass
+
+ def update_with_failure(self):
+ raise morphlib.execute.CommandFailure('git remote update origin', '')
+
+ def setUp(self):
+ self.repo_url = 'git://foo.bar/foo.git'
+ self.repo_path = '/tmp/foo'
+ self.repo = cachedrepo.CachedRepo(self.repo_url, self.repo_path)
+ self.repo._show_ref = self.show_ref
+ self.repo._rev_list = self.rev_list
+ self.repo._cat_file = self.cat_file
+ self.repo._copy_repository = self.copy_repository
+ self.repo._checkout_ref = self.checkout_ref
+ self.tempdir = morphlib.tempdir.Tempdir()
+
+ def tearDown(self):
+ self.tempdir.remove()
+
+ def test_constructor_sets_url_and_path(self):
+ self.assertEqual(self.repo.url, self.repo_url)
+ self.assertEqual(self.repo.path, self.repo_path)
+
+ def test_resolve_named_ref_master(self):
+ sha1 = self.repo.resolve_ref('master')
+ self.assertEqual(sha1, 'e28a23812eadf2fce6583b8819b9c5dbd36b9fb9')
+
+ def test_resolve_named_ref_baserock_morph(self):
+ sha1 = self.repo.resolve_ref('baserock/morph')
+ self.assertEqual(sha1, '8b780e2e6f102fcf400ff973396566d36d730501')
+
+ def test_fail_resolving_invalid_named_ref(self):
+ with self.assertRaises(cachedrepo.InvalidReferenceError):
+ self.repo.resolve_ref('foo/bar')
+
+ def test_resolve_sha1_ref(self):
+ sha1 = self.repo.resolve_ref(
+ 'e28a23812eadf2fce6583b8819b9c5dbd36b9fb9')
+ self.assertEqual(sha1, 'e28a23812eadf2fce6583b8819b9c5dbd36b9fb9')
+
+ def test_fail_resolving_an_invalid_sha1_ref(self):
+ with self.assertRaises(cachedrepo.InvalidReferenceError):
+ self.repo.resolve_ref('079bbfd447c8534e464ce5d40b80114c2022ebf4')
+
+ def test_cat_existing_file_in_existing_ref(self):
+ data = self.repo.cat('e28a23812eadf2fce6583b8819b9c5dbd36b9fb9',
+ 'foo.morph')
+ self.assertEqual(data, 'contents of foo.morph')
+
+ def test_fail_cat_file_in_invalid_ref(self):
+ with self.assertRaises(cachedrepo.InvalidReferenceError):
+ self.repo.cat('079bbfd447c8534e464ce5d40b80114c2022ebf4',
+ 'doesnt-matter-whether-this-file-exists')
+
+ def test_fail_cat_non_existent_file_in_existing_ref(self):
+ with self.assertRaises(IOError):
+ self.repo.cat('e28a23812eadf2fce6583b8819b9c5dbd36b9fb9',
+ 'file-that-does-not-exist')
+
+ def test_fail_cat_non_existent_file_in_invalid_ref(self):
+ with self.assertRaises(cachedrepo.InvalidReferenceError):
+ self.repo.cat('079bbfd447c8534e464ce5d40b80114c2022ebf4',
+ 'file-that-does-not-exist')
+
+ def test_fail_because_cat_in_named_ref_is_not_allowed(self):
+ with self.assertRaises(cachedrepo.UnresolvedNamedReferenceError):
+ self.repo.cat('master', 'doesnt-matter-wether-this-file-exists')
+
+ def test_fail_checkout_into_existing_directory(self):
+ with self.assertRaises(cachedrepo.CheckoutDirectoryExistsError):
+ self.repo.checkout('e28a23812eadf2fce6583b8819b9c5dbd36b9fb9',
+ self.tempdir.dirname)
+
+ def test_fail_checkout_from_named_ref_which_is_not_allowed(self):
+ with self.assertRaises(cachedrepo.UnresolvedNamedReferenceError):
+ self.repo.checkout('master',
+ self.tempdir.join('checkout-from-named-ref'))
+
+ def test_fail_checkout_from_invalid_ref(self):
+ with self.assertRaises(cachedrepo.InvalidReferenceError):
+ self.repo.checkout('079bbfd447c8534e464ce5d40b80114c2022ebf4',
+ self.tempdir.join('checkout-from-invalid-ref'))
+
+ def test_checkout_from_existing_ref_into_new_directory(self):
+ unpack_dir = self.tempdir.join('unpack-dir')
+ self.repo.checkout('e28a23812eadf2fce6583b8819b9c5dbd36b9fb9',
+ unpack_dir)
+ self.assertTrue(os.path.exists(unpack_dir))
+
+ morph_filename = os.path.join(unpack_dir, 'foo.morph')
+ self.assertTrue(os.path.exists(morph_filename))
+
+ def test_fail_checkout_due_to_copy_or_checkout_problem(self):
+ with self.assertRaises(cachedrepo.CheckoutError):
+ self.repo.checkout('a4da32f5a81c8bc6d660404724cedc3bc0914a75',
+ self.tempdir.join('failed-checkout'))
+
+ def test_successful_update(self):
+ self.repo._update = self.update_successfully
+ self.repo.update()
+
+ def test_failing_update(self):
+ self.repo._update = self.update_with_failure
+ with self.assertRaises(cachedrepo.UpdateError):
+ self.repo.update()