diff --git a/pr_agent/git_providers/gitlab_provider.py b/pr_agent/git_providers/gitlab_provider.py index e9db1a3740..0578c58345 100644 --- a/pr_agent/git_providers/gitlab_provider.py +++ b/pr_agent/git_providers/gitlab_provider.py @@ -954,22 +954,47 @@ def generate_link_to_relevant_line_number(self, suggestion) -> str: return "" #Clone related def _prepare_clone_url_with_token(self, repo_url_to_clone: str) -> str | None: - if "gitlab." not in repo_url_to_clone: - get_logger().error(f"Repo URL: {repo_url_to_clone} is not a valid gitlab URL.") - return None - (scheme, base_url) = repo_url_to_clone.split("gitlab.") access_token = getattr(self.gl, 'oauth_token', None) or getattr(self.gl, 'private_token', None) - if not all([scheme, access_token, base_url]): - get_logger().error(f"Either no access token found, or repo URL: {repo_url_to_clone} " - f"is missing prefix: {scheme} and/or base URL: {base_url}.") - return None + if not access_token: + return self._log_missing_clone_token() - #Note that the ""official"" method found here: - # https://docs.gitlab.com/user/profile/personal_access_tokens/#clone-repository-using-personal-access-token - # requires a username, which may not be applicable. - # The following solution is taken from: https://stackoverflow.com/questions/25409700/using-gitlab-token-to-clone-without-authentication/35003812#35003812 - # For example: For repo url: https://gitlab.codium-inc.com/qodo/autoscraper.git - # Then to clone one will issue: 'git clone https://oauth2:@gitlab.codium-inc.com/qodo/autoscraper.git' + # Note: GitLab instances are not always hosted under a gitlab.* domain. + # Build a clone URL that works with any host (e.g., gitlab.example.com). + if repo_url_to_clone.startswith(("http://", "https://")): + try: + parsed = urlparse(repo_url_to_clone) + if not parsed.scheme or not parsed.netloc: + raise ValueError("missing scheme or host") + netloc = parsed.netloc.split("@")[-1] + return f"{parsed.scheme}://oauth2:{access_token}@{netloc}{parsed.path}" + except Exception as e: + get_logger().error( + f"Repo URL: {repo_url_to_clone} could not be parsed for clone.", + artifact={"error": str(e)}, + ) + return None - clone_url = f"{scheme}oauth2:{access_token}@gitlab.{base_url}" - return clone_url + # Fallback for non-HTTP URLs (e.g., ssh or scp-style). + try: + if "@" in repo_url_to_clone and ":" in repo_url_to_clone and not repo_url_to_clone.startswith("ssh://"): + # Handle SCP-like URLs: git@gitlab.com:group/repo.git + repo_url_to_clone = "ssh://" + repo_url_to_clone.replace(":", "/", 1) + + parsed = urlparse(repo_url_to_clone) + if not parsed.netloc: + raise ValueError("missing host") + + netloc = parsed.netloc.split("@")[-1] + scheme = parsed.scheme if parsed.scheme else "https" + return f"{scheme}://oauth2:{access_token}@{netloc}{parsed.path}" + except Exception as e: + get_logger().error( + f"Repo URL: {repo_url_to_clone} could not be parsed for clone.", + artifact={"error": str(e)}, + ) + return None + + @staticmethod + def _log_missing_clone_token() -> None: + get_logger().error("No access token found for GitLab clone.") + return None diff --git a/tests/unittest/test_gitlab_provider.py b/tests/unittest/test_gitlab_provider.py index dadc0903cb..f401e3ed70 100644 --- a/tests/unittest/test_gitlab_provider.py +++ b/tests/unittest/test_gitlab_provider.py @@ -35,6 +35,7 @@ def gitlab_provider(self, mock_gitlab_client, mock_project): provider = GitLabProvider("https://gitlab.com/test/repo/-/merge_requests/1") provider.gl = mock_gitlab_client provider.id_project = "test/repo" + provider.gl.oauth_token = "fake_token" return provider def test_get_pr_file_content_success(self, gitlab_provider, mock_project): @@ -192,3 +193,43 @@ def test_compare_submodule_cached(self, gitlab_provider): assert first == second == [{"diff": "d"}] m_pbp.assert_called_once_with("grp/repo") proj.repository_compare.assert_called_once_with("old", "new") + + def test_prepare_clone_url_with_token_gitlab_com(self, gitlab_provider): + gitlab_provider.gl.oauth_token = "token123" + repo_url = "https://gitlab.com/group/repo.git" + + result = gitlab_provider._prepare_clone_url_with_token(repo_url) + + assert result == "https://oauth2:token123@gitlab.com/group/repo.git" + + def test_prepare_clone_url_with_token_custom_domain(self, gitlab_provider): + gitlab_provider.gl.oauth_token = "token123" + repo_url = "https://gitlab.example.com/group/repo.git" + + result = gitlab_provider._prepare_clone_url_with_token(repo_url) + + assert result == "https://oauth2:token123@gitlab.example.com/group/repo.git" + + def test_prepare_clone_url_with_token_invalid_url(self, gitlab_provider): + gitlab_provider.gl.oauth_token = "token123" + repo_url = "gitlab.example.com/group/repo.git" + + result = gitlab_provider._prepare_clone_url_with_token(repo_url) + + assert result is None + + def test_prepare_clone_url_with_token_scp_style(self, gitlab_provider): + gitlab_provider.gl.oauth_token = "token123" + repo_url = "git@gitlab.example.com:group/repo.git" + + result = gitlab_provider._prepare_clone_url_with_token(repo_url) + + assert result == "ssh://oauth2:token123@gitlab.example.com/group/repo.git" + + def test_prepare_clone_url_with_token_ssh_url(self, gitlab_provider): + gitlab_provider.gl.oauth_token = "token123" + repo_url = "ssh://git@gitlab.example.com/group/repo.git" + + result = gitlab_provider._prepare_clone_url_with_token(repo_url) + + assert result == "ssh://oauth2:token123@gitlab.example.com/group/repo.git"