diff --git a/repo2docker/contentproviders/__init__.py b/repo2docker/contentproviders/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/repo2docker/contentproviders/base.py b/repo2docker/contentproviders/base.py new file mode 100644 index 00000000..dbbc1215 --- /dev/null +++ b/repo2docker/contentproviders/base.py @@ -0,0 +1,26 @@ +""" +Base classes for repo2docker ContentProviders + +ContentProviders accept a `spec` of various kinds, and +provide the contents from the spec to a given output directory. +""" +class ContentProviderException(Exception): + """Exception raised when a ContentProvider can not provide content + """ + pass + +class ContentProvider: + kind = "" + + def provide(self, spec, output_dir, yield_output=False): + """Provide the contents of given spec to output_dir + + This is a generator, and so should be yielded from or iterated over. + + Arguments: + spec -- Dict / String specification understood by this ContentProvider + output_dir {string} -- Path to output directory (must already exist) + yield_output {bool} -- If True, return output line by line. If not, output just goes to stdout. + """ + raise NotImplementedError() + diff --git a/repo2docker/contentproviders/git.py b/repo2docker/contentproviders/git.py new file mode 100644 index 00000000..3c6b1026 --- /dev/null +++ b/repo2docker/contentproviders/git.py @@ -0,0 +1,28 @@ +import subprocess + +from .base import ContentProvider, ContentProviderException +from ..utils import execute_cmd + +class GitContentProvider(ContentProvider): + """Provides contents of a git repository (optionally at a given ref) + """ + kind = "git" + + def provide(self, spec, output_dir, yield_output=False): + url = spec['url'] + ref = spec.get('ref', None) + try: + for line in execute_cmd(['git', 'clone', url, output_dir], + capture=yield_output): + yield line + except subprocess.CalledProcessError as e: + raise ContentProviderException("Failed to clone repository!") from e + + if ref: + try: + for line in execute_cmd(['git', 'reset', '--hard', ref], + cwd=output_dir, + capture=yield_output): + yield line + except subprocess.CalledProcessError: + raise ContentProviderException("Failed to checkout ref {}!".format(ref)) from e diff --git a/tests/contentproviders/test_git.py b/tests/contentproviders/test_git.py new file mode 100644 index 00000000..529b9115 --- /dev/null +++ b/tests/contentproviders/test_git.py @@ -0,0 +1,38 @@ +from contextlib import contextmanager +import shutil +import os +import subprocess +from tempfile import TemporaryDirectory +from repo2docker.contentproviders.git import GitContentProvider + + +@contextmanager +def git_repo(): + """ + Makes a dummy git repo in which user can perform git operations + + Should be used as a contextmanager, it will delete directory when done + """ + + with TemporaryDirectory() as gitdir: + subprocess.check_call(['git', 'init'], cwd=gitdir) + + yield gitdir + +def test_clone(): + """Test simple git clone to a target dir + """ + with git_repo() as upstream: + with open(os.path.join(upstream, 'test'), 'w') as f: + f.write("Hello") + + subprocess.check_call(['git', 'add', 'test'], cwd=upstream) + subprocess.check_call(['git', 'commit', '-m', 'Test commit'], cwd=upstream) + + with TemporaryDirectory() as clone_dir: + spec = { + 'url': upstream + } + for _ in GitContentProvider().provide(spec, clone_dir, False): + pass + assert os.path.exists(os.path.join(clone_dir, 'test'))