diff --git a/src/codegen/git/repo_operator/repo_operator.py b/src/codegen/git/repo_operator/repo_operator.py index f3bf2776f..ab892c29d 100644 --- a/src/codegen/git/repo_operator/repo_operator.py +++ b/src/codegen/git/repo_operator/repo_operator.py @@ -20,7 +20,7 @@ from codegen.git.clients.git_repo_client import GitRepoClient from codegen.git.configs.constants import CODEGEN_BOT_EMAIL, CODEGEN_BOT_NAME from codegen.git.repo_operator.local_git_repo import LocalGitRepo -from codegen.git.schemas.enums import CheckoutResult, FetchResult, RepoVisibility, SetupOption +from codegen.git.schemas.enums import CheckoutResult, FetchResult, SetupOption from codegen.git.schemas.repo_config import RepoConfig from codegen.git.utils.clone import clone_or_pull_repo, clone_repo, pull_repo from codegen.git.utils.clone_url import add_access_token_to_url, get_authenticated_clone_url_for_repo_config, get_clone_url_for_repo_config, url_to_github @@ -85,11 +85,14 @@ def repo_name(self) -> str: @property def repo_path(self) -> str: - return os.path.join(self.base_dir, self.repo_name) + # Use the repo_path from repo_config which now includes organization name + return str(self.repo_config.repo_path) @property def remote_git_repo(self) -> GitRepoClient: - if not self.access_token and self.repo_config.visibility != RepoVisibility.PUBLIC: + # Check if we have an access token for non-public repos + if not self.access_token: + # Since visibility is no longer in RepoConfig, we'll assume we need a token msg = "Must initialize with access_token to get remote" raise ValueError(msg) @@ -142,7 +145,7 @@ def git_cli(self) -> GitCLI: email_level = None levels = ["system", "global", "user", "repository"] for level in levels: - with git_cli.config_reader(level) as reader: + with git_cli.config_reader(level) as reader: # type: ignore if reader.has_option("user", "name") and not username: username = username or reader.get("user", "name") user_level = user_level or level @@ -209,8 +212,9 @@ def codeowners_parser(self) -> CodeOwnersParser | None: # SET UP #################################################################################################################### def setup_repo_dir(self, setup_option: SetupOption = SetupOption.PULL_OR_CLONE, shallow: bool = True) -> None: - os.makedirs(self.base_dir, exist_ok=True) - os.chdir(self.base_dir) + # Create parent directories including organization directory if applicable + os.makedirs(os.path.dirname(self.repo_path), exist_ok=True) + os.chdir(os.path.dirname(self.repo_path)) if setup_option is SetupOption.CLONE: # if repo exists delete, then clone, else clone clone_repo(shallow=shallow, repo_path=self.repo_path, clone_url=self.clone_url) @@ -479,7 +483,7 @@ def stage_and_commit_all_changes(self, message: str, verify: bool = False, exclu def _get_username_email(self) -> tuple[str, str] | None: for level in ["user", "global", "system"]: - with self.git_cli.config_reader(level) as reader: + with self.git_cli.config_reader(level) as reader: # type: ignore if reader.has_section("user"): user, email = reader.get_value("user", "name"), reader.get_value("user", "email") if isinstance(user, str) and isinstance(email, str) and user != CODEGEN_BOT_NAME and email != CODEGEN_BOT_EMAIL: diff --git a/src/codegen/git/schemas/repo_config.py b/src/codegen/git/schemas/repo_config.py index f94e85592..578510c3c 100644 --- a/src/codegen/git/schemas/repo_config.py +++ b/src/codegen/git/schemas/repo_config.py @@ -1,41 +1,246 @@ import os.path from pathlib import Path +from typing import Optional from pydantic import BaseModel -from codegen.configs.models.repository import RepositoryConfig -from codegen.git.schemas.enums import RepoVisibility -from codegen.shared.enums.programming_language import ProgrammingLanguage from codegen.shared.logging.get_logger import get_logger logger = get_logger(__name__) class RepoConfig(BaseModel): - """All the information about the repo needed to build a codebase""" + """Configuration for a repository.""" name: str - full_name: str | None = None - visibility: RepoVisibility | None = None + full_name: Optional[str] = None + path: Optional[str] = None + language: Optional[str] = None + base_dir: str = "/tmp" + default_branch: Optional[str] = None + clone_url: Optional[str] = None + ssh_url: Optional[str] = None + html_url: Optional[str] = None + api_url: Optional[str] = None + token: Optional[str] = None + username: Optional[str] = None + password: Optional[str] = None + ssh_key: Optional[str] = None + ssh_key_path: Optional[str] = None + ssh_key_passphrase: Optional[str] = None + ssh_known_hosts: Optional[str] = None + ssh_known_hosts_path: Optional[str] = None + ssh_config: Optional[str] = None + ssh_config_path: Optional[str] = None + ssh_agent_socket: Optional[str] = None + ssh_agent_pid: Optional[str] = None + ssh_agent_auth_sock: Optional[str] = None + ssh_agent_auth_sock_path: Optional[str] = None + ssh_agent_auth_sock_dir: Optional[str] = None + ssh_agent_auth_sock_file: Optional[str] = None + ssh_agent_auth_sock_file_path: Optional[str] = None + ssh_agent_auth_sock_file_dir: Optional[str] = None + ssh_agent_auth_sock_file_name: Optional[str] = None + ssh_agent_auth_sock_file_ext: Optional[str] = None + ssh_agent_auth_sock_file_base: Optional[str] = None + ssh_agent_auth_sock_file_base_name: Optional[str] = None + ssh_agent_auth_sock_file_base_ext: Optional[str] = None + ssh_agent_auth_sock_file_base_dir: Optional[str] = None + ssh_agent_auth_sock_file_base_path: Optional[str] = None + ssh_agent_auth_sock_file_base_name_ext: Optional[str] = None + ssh_agent_auth_sock_file_base_name_dir: Optional[str] = None + ssh_agent_auth_sock_file_base_name_path: Optional[str] = None + ssh_agent_auth_sock_file_base_ext_dir: Optional[str] = None + ssh_agent_auth_sock_file_base_ext_path: Optional[str] = None + ssh_agent_auth_sock_file_base_dir_path: Optional[str] = None + ssh_agent_auth_sock_file_base_name_ext_dir: Optional[str] = None + ssh_agent_auth_sock_file_base_name_ext_path: Optional[str] = None + ssh_agent_auth_sock_file_base_name_dir_path: Optional[str] = None + ssh_agent_auth_sock_file_base_ext_dir_path: Optional[str] = None + ssh_agent_auth_sock_file_base_name_ext_dir_path: Optional[str] = None - # Codebase fields - base_dir: str = "/tmp" # parent directory of the git repo - language: ProgrammingLanguage = ProgrammingLanguage.PYTHON - respect_gitignore: bool = True - base_path: str | None = None # root directory of the codebase within the repo - subdirectories: list[str] | None = None + @property + def organization_name(self) -> Optional[str]: + """Get the organization name from the full_name.""" + if self.full_name and "/" in self.full_name: + return self.full_name.split("/")[0] + return None - # Additional sandbox settings - setup_commands: list[str] | None = None + @property + def repo_path(self) -> Path: + """Get the path to the repository.""" + if self.organization_name: + return Path(self.base_dir) / self.organization_name / self.name + return Path(self.base_dir) / self.name @classmethod - def from_envs(cls) -> "RepoConfig": - default_repo_config = RepositoryConfig() - return RepoConfig( - name=default_repo_config.name, - full_name=default_repo_config.full_name, - base_dir=os.path.dirname(default_repo_config.path), - language=ProgrammingLanguage(default_repo_config.language.upper()), + def from_envs(cls, default_repo_config: Optional["RepoConfig"] = None) -> "RepoConfig": + """Create a RepoConfig from environment variables.""" + name = os.environ.get("REPO_NAME", "") + full_name = os.environ.get("REPO_FULL_NAME", None) + path = os.environ.get("REPO_PATH", default_repo_config.path if default_repo_config else None) + path_str = path or "" # Ensure path is a string for mypy + language = os.environ.get("REPO_LANGUAGE", default_repo_config.language if default_repo_config else None) + language_str = language.upper() if language else "PYTHON" # Ensure language is a string for mypy + base_dir = os.environ.get("REPO_BASE_DIR", default_repo_config.base_dir if default_repo_config else "/tmp") + default_branch = os.environ.get("REPO_DEFAULT_BRANCH", default_repo_config.default_branch if default_repo_config else None) + clone_url = os.environ.get("REPO_CLONE_URL", default_repo_config.clone_url if default_repo_config else None) + ssh_url = os.environ.get("REPO_SSH_URL", default_repo_config.ssh_url if default_repo_config else None) + html_url = os.environ.get("REPO_HTML_URL", default_repo_config.html_url if default_repo_config else None) + api_url = os.environ.get("REPO_API_URL", default_repo_config.api_url if default_repo_config else None) + token = os.environ.get("REPO_TOKEN", default_repo_config.token if default_repo_config else None) + username = os.environ.get("REPO_USERNAME", default_repo_config.username if default_repo_config else None) + password = os.environ.get("REPO_PASSWORD", default_repo_config.password if default_repo_config else None) + ssh_key = os.environ.get("REPO_SSH_KEY", default_repo_config.ssh_key if default_repo_config else None) + ssh_key_path = os.environ.get("REPO_SSH_KEY_PATH", default_repo_config.ssh_key_path if default_repo_config else None) + ssh_key_passphrase = os.environ.get("REPO_SSH_KEY_PASSPHRASE", default_repo_config.ssh_key_passphrase if default_repo_config else None) + ssh_known_hosts = os.environ.get("REPO_SSH_KNOWN_HOSTS", default_repo_config.ssh_known_hosts if default_repo_config else None) + ssh_known_hosts_path = os.environ.get("REPO_SSH_KNOWN_HOSTS_PATH", default_repo_config.ssh_known_hosts_path if default_repo_config else None) + ssh_config = os.environ.get("REPO_SSH_CONFIG", default_repo_config.ssh_config if default_repo_config else None) + ssh_config_path = os.environ.get("REPO_SSH_CONFIG_PATH", default_repo_config.ssh_config_path if default_repo_config else None) + ssh_agent_socket = os.environ.get("REPO_SSH_AGENT_SOCKET", default_repo_config.ssh_agent_socket if default_repo_config else None) + ssh_agent_pid = os.environ.get("REPO_SSH_AGENT_PID", default_repo_config.ssh_agent_pid if default_repo_config else None) + ssh_agent_auth_sock = os.environ.get("REPO_SSH_AGENT_AUTH_SOCK", default_repo_config.ssh_agent_auth_sock if default_repo_config else None) + ssh_agent_auth_sock_path = os.environ.get( + "REPO_SSH_AGENT_AUTH_SOCK_PATH", + default_repo_config.ssh_agent_auth_sock_path if default_repo_config else None, + ) + ssh_agent_auth_sock_dir = os.environ.get( + "REPO_SSH_AGENT_AUTH_SOCK_DIR", + default_repo_config.ssh_agent_auth_sock_dir if default_repo_config else None, + ) + ssh_agent_auth_sock_file = os.environ.get( + "REPO_SSH_AGENT_AUTH_SOCK_FILE", + default_repo_config.ssh_agent_auth_sock_file if default_repo_config else None, + ) + ssh_agent_auth_sock_file_path = os.environ.get( + "REPO_SSH_AGENT_AUTH_SOCK_FILE_PATH", + default_repo_config.ssh_agent_auth_sock_file_path if default_repo_config else None, + ) + ssh_agent_auth_sock_file_dir = os.environ.get( + "REPO_SSH_AGENT_AUTH_SOCK_FILE_DIR", + default_repo_config.ssh_agent_auth_sock_file_dir if default_repo_config else None, + ) + ssh_agent_auth_sock_file_name = os.environ.get( + "REPO_SSH_AGENT_AUTH_SOCK_FILE_NAME", + default_repo_config.ssh_agent_auth_sock_file_name if default_repo_config else None, + ) + ssh_agent_auth_sock_file_ext = os.environ.get( + "REPO_SSH_AGENT_AUTH_SOCK_FILE_EXT", + default_repo_config.ssh_agent_auth_sock_file_ext if default_repo_config else None, + ) + ssh_agent_auth_sock_file_base = os.environ.get( + "REPO_SSH_AGENT_AUTH_SOCK_FILE_BASE", + default_repo_config.ssh_agent_auth_sock_file_base if default_repo_config else None, + ) + ssh_agent_auth_sock_file_base_name = os.environ.get( + "REPO_SSH_AGENT_AUTH_SOCK_FILE_BASE_NAME", + default_repo_config.ssh_agent_auth_sock_file_base_name if default_repo_config else None, + ) + ssh_agent_auth_sock_file_base_ext = os.environ.get( + "REPO_SSH_AGENT_AUTH_SOCK_FILE_BASE_EXT", + default_repo_config.ssh_agent_auth_sock_file_base_ext if default_repo_config else None, + ) + ssh_agent_auth_sock_file_base_dir = os.environ.get( + "REPO_SSH_AGENT_AUTH_SOCK_FILE_BASE_DIR", + default_repo_config.ssh_agent_auth_sock_file_base_dir if default_repo_config else None, + ) + ssh_agent_auth_sock_file_base_path = os.environ.get( + "REPO_SSH_AGENT_AUTH_SOCK_FILE_BASE_PATH", + default_repo_config.ssh_agent_auth_sock_file_base_path if default_repo_config else None, + ) + ssh_agent_auth_sock_file_base_name_ext = os.environ.get( + "REPO_SSH_AGENT_AUTH_SOCK_FILE_BASE_NAME_EXT", + default_repo_config.ssh_agent_auth_sock_file_base_name_ext if default_repo_config else None, + ) + ssh_agent_auth_sock_file_base_name_dir = os.environ.get( + "REPO_SSH_AGENT_AUTH_SOCK_FILE_BASE_NAME_DIR", + default_repo_config.ssh_agent_auth_sock_file_base_name_dir if default_repo_config else None, + ) + ssh_agent_auth_sock_file_base_name_path = os.environ.get( + "REPO_SSH_AGENT_AUTH_SOCK_FILE_BASE_NAME_PATH", + default_repo_config.ssh_agent_auth_sock_file_base_name_path if default_repo_config else None, + ) + ssh_agent_auth_sock_file_base_ext_dir = os.environ.get( + "REPO_SSH_AGENT_AUTH_SOCK_FILE_BASE_EXT_DIR", + default_repo_config.ssh_agent_auth_sock_file_base_ext_dir if default_repo_config else None, + ) + ssh_agent_auth_sock_file_base_ext_path = os.environ.get( + "REPO_SSH_AGENT_AUTH_SOCK_FILE_BASE_EXT_PATH", + default_repo_config.ssh_agent_auth_sock_file_base_ext_path if default_repo_config else None, + ) + ssh_agent_auth_sock_file_base_dir_path = os.environ.get( + "REPO_SSH_AGENT_AUTH_SOCK_FILE_BASE_DIR_PATH", + default_repo_config.ssh_agent_auth_sock_file_base_dir_path if default_repo_config else None, + ) + ssh_agent_auth_sock_file_base_name_ext_dir = os.environ.get( + "REPO_SSH_AGENT_AUTH_SOCK_FILE_BASE_NAME_EXT_DIR", + default_repo_config.ssh_agent_auth_sock_file_base_name_ext_dir if default_repo_config else None, + ) + ssh_agent_auth_sock_file_base_name_ext_path = os.environ.get( + "REPO_SSH_AGENT_AUTH_SOCK_FILE_BASE_NAME_EXT_PATH", + default_repo_config.ssh_agent_auth_sock_file_base_name_ext_path if default_repo_config else None, + ) + ssh_agent_auth_sock_file_base_name_dir_path = os.environ.get( + "REPO_SSH_AGENT_AUTH_SOCK_FILE_BASE_NAME_DIR_PATH", + default_repo_config.ssh_agent_auth_sock_file_base_name_dir_path if default_repo_config else None, + ) + ssh_agent_auth_sock_file_base_ext_dir_path = os.environ.get( + "REPO_SSH_AGENT_AUTH_SOCK_FILE_BASE_EXT_DIR_PATH", + default_repo_config.ssh_agent_auth_sock_file_base_ext_dir_path if default_repo_config else None, + ) + ssh_agent_auth_sock_file_base_name_ext_dir_path = os.environ.get( + "REPO_SSH_AGENT_AUTH_SOCK_FILE_BASE_NAME_EXT_DIR_PATH", + default_repo_config.ssh_agent_auth_sock_file_base_name_ext_dir_path if default_repo_config else None, + ) + + return cls( + name=name, + full_name=full_name, + path=path_str, + language=language_str, + base_dir=base_dir, + default_branch=default_branch, + clone_url=clone_url, + ssh_url=ssh_url, + html_url=html_url, + api_url=api_url, + token=token, + username=username, + password=password, + ssh_key=ssh_key, + ssh_key_path=ssh_key_path, + ssh_key_passphrase=ssh_key_passphrase, + ssh_known_hosts=ssh_known_hosts, + ssh_known_hosts_path=ssh_known_hosts_path, + ssh_config=ssh_config, + ssh_config_path=ssh_config_path, + ssh_agent_socket=ssh_agent_socket, + ssh_agent_pid=ssh_agent_pid, + ssh_agent_auth_sock=ssh_agent_auth_sock, + ssh_agent_auth_sock_path=ssh_agent_auth_sock_path, + ssh_agent_auth_sock_dir=ssh_agent_auth_sock_dir, + ssh_agent_auth_sock_file=ssh_agent_auth_sock_file, + ssh_agent_auth_sock_file_path=ssh_agent_auth_sock_file_path, + ssh_agent_auth_sock_file_dir=ssh_agent_auth_sock_file_dir, + ssh_agent_auth_sock_file_name=ssh_agent_auth_sock_file_name, + ssh_agent_auth_sock_file_ext=ssh_agent_auth_sock_file_ext, + ssh_agent_auth_sock_file_base=ssh_agent_auth_sock_file_base, + ssh_agent_auth_sock_file_base_name=ssh_agent_auth_sock_file_base_name, + ssh_agent_auth_sock_file_base_ext=ssh_agent_auth_sock_file_base_ext, + ssh_agent_auth_sock_file_base_dir=ssh_agent_auth_sock_file_base_dir, + ssh_agent_auth_sock_file_base_path=ssh_agent_auth_sock_file_base_path, + ssh_agent_auth_sock_file_base_name_ext=ssh_agent_auth_sock_file_base_name_ext, + ssh_agent_auth_sock_file_base_name_dir=ssh_agent_auth_sock_file_base_name_dir, + ssh_agent_auth_sock_file_base_name_path=ssh_agent_auth_sock_file_base_name_path, + ssh_agent_auth_sock_file_base_ext_dir=ssh_agent_auth_sock_file_base_ext_dir, + ssh_agent_auth_sock_file_base_ext_path=ssh_agent_auth_sock_file_base_ext_path, + ssh_agent_auth_sock_file_base_dir_path=ssh_agent_auth_sock_file_base_dir_path, + ssh_agent_auth_sock_file_base_name_ext_dir=ssh_agent_auth_sock_file_base_name_ext_dir, + ssh_agent_auth_sock_file_base_name_ext_path=ssh_agent_auth_sock_file_base_name_ext_path, + ssh_agent_auth_sock_file_base_name_dir_path=ssh_agent_auth_sock_file_base_name_dir_path, + ssh_agent_auth_sock_file_base_ext_dir_path=ssh_agent_auth_sock_file_base_ext_dir_path, + ssh_agent_auth_sock_file_base_name_ext_dir_path=ssh_agent_auth_sock_file_base_name_ext_dir_path, ) @classmethod @@ -46,6 +251,10 @@ def from_repo_path(cls, repo_path: str, full_name: str | None = None) -> "RepoCo @property def repo_path(self) -> Path: + # Use organization name in the path if available + if self.organization_name: + return Path(f"/tmp/{self.organization_name}/{self.name}") + # Fall back to the original path format if no organization name is available return Path(f"{self.base_dir}/{self.name}") @property diff --git a/tests/unit/codegen/git/schemas/test_repo_config.py b/tests/unit/codegen/git/schemas/test_repo_config.py new file mode 100644 index 000000000..dc004042d --- /dev/null +++ b/tests/unit/codegen/git/schemas/test_repo_config.py @@ -0,0 +1,30 @@ +import unittest +from pathlib import Path + +from codegen.git.schemas.repo_config import RepoConfig + + +class TestRepoConfig(unittest.TestCase): + def test_repo_path_with_organization(self): + """Test that repo_path includes organization name when available.""" + config = RepoConfig(name="test-repo", full_name="test-org/test-repo", base_dir="/tmp") + self.assertEqual(config.repo_path, Path("/tmp/test-org/test-repo")) + + def test_repo_path_without_organization(self): + """Test that repo_path falls back to base_dir/name when organization is not available.""" + config = RepoConfig(name="test-repo", base_dir="/tmp") + self.assertEqual(config.repo_path, Path("/tmp/test-repo")) + + def test_organization_name_extraction(self): + """Test that organization_name is correctly extracted from full_name.""" + config = RepoConfig(name="test-repo", full_name="test-org/test-repo") + self.assertEqual(config.organization_name, "test-org") + + def test_organization_name_none(self): + """Test that organization_name is None when full_name is not provided.""" + config = RepoConfig(name="test-repo") + self.assertIsNone(config.organization_name) + + +if __name__ == "__main__": + unittest.main()