diff --git a/renku/core/session/session.py b/renku/core/session/session.py index 95e76b1edc..59266fa10e 100644 --- a/renku/core/session/session.py +++ b/renku/core/session/session.py @@ -158,6 +158,9 @@ def session_start( """ from renku.domain_model.project_context import project_context + if project_context.repository.head.detached: + raise errors.SessionStartError("Cannot start a session from a detached HEAD. Check out a branch first.") + # NOTE: The Docker client in Python requires the parameters below to be a list and will fail with a tuple. # Click will convert parameters with the flag "many" set to True to tuples. kwargs["security_opt"] = list(kwargs.get("security_opt", [])) diff --git a/renku/infrastructure/repository.py b/renku/infrastructure/repository.py index 28afbdaaeb..8a99c9bc8b 100644 --- a/renku/infrastructure/repository.py +++ b/renku/infrastructure/repository.py @@ -1554,6 +1554,11 @@ def reference(self) -> Optional[Reference]: except (git.GitError, TypeError): return None + @property + def detached(self) -> bool: + """True if the reference is to a commit and not a branch.""" + return self._reference.is_detached + class RemoteReference(Reference): """A git remote reference.""" @@ -1681,8 +1686,11 @@ def set_url(self, url: str): _run_git_command(self._repository, "remote", "set-url", self.name, url) @property - def head(self) -> str: + def head(self) -> Optional[str]: """The head commit of the remote.""" + if self._repository.head.is_detached: + return None + self._remote.fetch() return _run_git_command(self._repository, "rev-parse", f"{self._remote.name}/{self._repository.active_branch}") diff --git a/tests/cli/test_session.py b/tests/cli/test_session.py index 6f2e15ed83..287cb5375c 100644 --- a/tests/cli/test_session.py +++ b/tests/cli/test_session.py @@ -175,3 +175,18 @@ def test_session_list_format(runner, project, dummy_session_provider, format, ou assert 0 == result.exit_code, format_result_exception(result) assert length == len(result.output.splitlines()) assert output in result.output + + +def test_session_detached_start(runner, project, dummy_session_provider): + """Test starting a session in a detached HEAD repository.""" + # NOTE: Make a dummy commit for project to have two commits + (project.repository.path / "README.md").write_text("changes") + project.repository.add(all=True) + project.repository.commit("dummy commit") + + project.repository.checkout("HEAD~") + + result = runner.invoke(cli, ["session", "start", "-p", "dummy"]) + + assert 1 == result.exit_code, format_result_exception(result) + assert "Cannot start a session from a detached HEAD" in result.output diff --git a/tests/core/metadata/test_repository.py b/tests/core/metadata/test_repository.py index 8a98fd6a59..3ba5c0120a 100644 --- a/tests/core/metadata/test_repository.py +++ b/tests/core/metadata/test_repository.py @@ -326,3 +326,23 @@ def test_ignored_paths(paths, ignored, project): from renku.domain_model.project_context import project_context assert project_context.repository.get_ignored_paths(*paths) == ignored + + +def test_remote(git_repository): + """Test get remote of a repository.""" + assert 1 == len(git_repository.remotes) + + remote = git_repository.remotes[0] + + assert "origin" == remote.name + assert "8853e0c1112e512c36db9cc76faff560b655e5d5" == remote.head + + +def test_remote_detached(git_repository): + """Test get remote of a repository with detached head.""" + git_repository.checkout("HEAD~") + + remote = git_repository.remotes[0] + + assert "origin" == remote.name + assert remote.head is None