diff --git a/jupyterlab_git/__init__.py b/jupyterlab_git/__init__.py index f168e44d9..adc977a2e 100644 --- a/jupyterlab_git/__init__.py +++ b/jupyterlab_git/__init__.py @@ -19,7 +19,7 @@ class JupyterLabGit(Configurable): help='Actions to be taken after a git command. Each action takes a list of commands to execute (strings). Supported actions: post_init', config=True, trait=List( - trait=Unicode, + trait=Unicode(), help='List of commands to run. E.g. ["touch baz.py"]' ) # TODO Validate @@ -35,7 +35,7 @@ def load_jupyter_server_extension(nbapp): """Load the Jupyter server extension. """ - user_custom_actions = JupyterLabGit(config=nbapp.config).actions - git = Git(nbapp.web_app.settings['contents_manager'], user_custom_actions) + config = JupyterLabGit(config=nbapp.config) + git = Git(nbapp.web_app.settings['contents_manager'], config) nbapp.web_app.settings["git"] = git setup_handlers(nbapp.web_app) diff --git a/jupyterlab_git/git.py b/jupyterlab_git/git.py index ccfa94f0a..8b275e945 100644 --- a/jupyterlab_git/git.py +++ b/jupyterlab_git/git.py @@ -143,10 +143,10 @@ class Git: A single parent class containing all of the individual git methods in it. """ - def __init__(self, contents_manager, user_custom_actions): + def __init__(self, contents_manager, config=None): self.contents_manager = contents_manager self.root_dir = os.path.expanduser(contents_manager.root_dir) - self.user_custom_actions = user_custom_actions + self._config = config async def config(self, top_repo_path, **kwargs): """Get or set Git options. @@ -878,28 +878,46 @@ async def init(self, current_path): code, _, error = await execute( cmd, cwd=cwd ) + + actions = None if code == 0: - code, _, error = await self._maybe_run_actions('post_init', cwd) + code, actions = await self._maybe_run_actions('post_init', cwd) if code != 0: - return {"code": code, "command": " ".join(cmd), "message": error} - return {"code": code} + return {"code": code, "command": " ".join(cmd), "message": error, "actions": actions} + return {"code": code, "actions": actions} async def _maybe_run_actions(self, name, cwd): code = 0 - stdout = None - stderr = None - if name in self.user_custom_actions: - actions_list = self.user_custom_actions[name] + actions = None + if self._config and name in self._config.actions: + actions = [] + actions_list = self._config.actions[name] for action in actions_list: - # We trust the actions as they were passed via a config and not the UI - code, stdout, stderr = await execute( - shlex.split(action), cwd=cwd - ) - # After any failure, stop + try: + # We trust the actions as they were passed via a config and not the UI + code, stdout, stderr = await execute( + shlex.split(action), cwd=cwd + ) + actions.append({ + 'cmd': action, + 'code': code, + 'stdout': stdout, + 'stderr': stderr + }) + # After any failure, stop + except Exception as e: + code = 1 + action.append({ + 'cmd': action, + 'code': 1, + 'stdout': None, + 'stderr': 'Exception: {}'.format(e) + }) if code != 0: break - return code, stdout, stderr + + return code, actions def _is_remote_branch(self, branch_reference): """Check if given branch is remote branch by comparing with 'remotes/', @@ -1133,7 +1151,7 @@ def remote_add(self, top_repo_path, url, name=DEFAULT_REMOTE_NAME): async def version(self): """Return the Git command version. - + If an error occurs, return None. """ command = ["git", "--version"] @@ -1142,12 +1160,12 @@ async def version(self): version = GIT_VERSION_REGEX.match(output) if version is not None: return version.group('version') - + return None async def tags(self, current_path): """List all tags of the git repository. - + current_path: str Git path repository """ @@ -1160,7 +1178,7 @@ async def tags(self, current_path): async def tag_checkout(self, current_path, tag): """Checkout the git repository at a given tag. - + current_path: str Git path repository tag : str diff --git a/jupyterlab_git/tests/test_init.py b/jupyterlab_git/tests/test_init.py new file mode 100644 index 000000000..47de76851 --- /dev/null +++ b/jupyterlab_git/tests/test_init.py @@ -0,0 +1,88 @@ +import os +from subprocess import CalledProcessError +from unittest.mock import Mock, call, patch + +import pytest +import tornado + +from jupyterlab_git import JupyterLabGit +from jupyterlab_git.git import Git + +from .testutils import FakeContentManager, maybe_future + + +@pytest.mark.asyncio +async def test_init(): + with patch("jupyterlab_git.git.execute") as mock_execute: + # Given + mock_execute.return_value = maybe_future((0, "", "")) + + # When + actual_response = await Git(FakeContentManager("/bin")).init("test_curr_path") + + mock_execute.assert_called_once_with( + ["git", "init"], cwd=os.path.join("/bin", "test_curr_path") + ) + + assert {"code": 0, "actions": None} == actual_response + + +@pytest.mark.asyncio +async def test_init_and_post_init(): + with patch("jupyterlab_git.git.execute") as mock_execute: + # Given + mock_execute.side_effect = [ + maybe_future((0, "", "")), + maybe_future((0, "hello", "")), + ] + + # When + actual_response = await Git( + FakeContentManager("/bin"), + JupyterLabGit(actions={"post_init": ['echo "hello"']}), + ).init("test_curr_path") + + mock_execute.assert_called_with( + ["echo", "hello"], cwd=os.path.join("/bin", "test_curr_path") + ) + + assert { + "code": 0, + "actions": [ + {"cmd": 'echo "hello"', "code": 0, "stderr": "", "stdout": "hello"} + ], + } == actual_response + + +@pytest.mark.asyncio +async def test_init_and_post_init_fail(): + with patch("jupyterlab_git.git.execute") as mock_execute: + # Given + mock_execute.side_effect = [ + maybe_future((0, "", "")), + maybe_future((1, "", "not_there: command not found")), + ] + + # When + actual_response = await Git( + FakeContentManager("/bin"), + JupyterLabGit(actions={"post_init": ["not_there arg"]}), + ).init("test_curr_path") + + mock_execute.assert_called_with( + ["not_there", "arg"], cwd=os.path.join("/bin", "test_curr_path") + ) + + assert { + "code": 1, + "message": "", + "command": "git init", + "actions": [ + { + "stderr": "not_there: command not found", + "stdout": "", + "code": 1, + "cmd": "not_there arg", + } + ], + } == actual_response