Clone a git repository as a new A0 project. Token is used only for cloning via http header.
(name: str, git_url: str, git_token: str, data: BasicProjectData)
| 114 | |
| 115 | |
| 116 | def clone_git_project(name: str, git_url: str, git_token: str, data: BasicProjectData): |
| 117 | """Clone a git repository as a new A0 project. Token is used only for cloning via http header.""" |
| 118 | from helpers import git |
| 119 | |
| 120 | extended_data = _project_extended_data_for_save(data) |
| 121 | mcp_servers = data.get("mcp_servers") if isinstance(data, dict) else None |
| 122 | |
| 123 | abs_path = files.create_dir_safe( |
| 124 | files.get_abs_path(PROJECTS_PARENT_DIR, name), rename_format="{name}_{number}" |
| 125 | ) |
| 126 | actual_name = files.basename(abs_path) |
| 127 | |
| 128 | try: |
| 129 | # Clone with token via http.extraHeader (token never in URL or git config) |
| 130 | git.clone_repo(git_url, abs_path, token=git_token) |
| 131 | clean_url = git.strip_auth_from_url(git_url) |
| 132 | |
| 133 | # Check if cloned repo already has .a0proj |
| 134 | meta_path = os.path.join(abs_path, PROJECT_META_DIR, PROJECT_HEADER_FILE) |
| 135 | if os.path.exists(meta_path): |
| 136 | # Merge: keep cloned content, override only user-specified fields |
| 137 | cloned_header: BasicProjectData = dirty_json.parse(files.read_file(meta_path)) # type: ignore |
| 138 | cloned_header["title"] = data.get("title") or cloned_header.get("title", "") |
| 139 | cloned_header["color"] = data.get("color") or cloned_header.get("color", "") |
| 140 | cloned_header["git_url"] = clean_url |
| 141 | save_project_header(actual_name, cloned_header) |
| 142 | else: |
| 143 | # New project: create meta folders and save header |
| 144 | create_project_meta_folders(actual_name) |
| 145 | data = _normalizeBasicData(data) |
| 146 | data["git_url"] = clean_url |
| 147 | save_project_header(actual_name, data) |
| 148 | |
| 149 | if mcp_servers: |
| 150 | save_project_mcp_servers(actual_name, mcp_servers) |
| 151 | save_project_extended_data(actual_name, extended_data) |
| 152 | |
| 153 | return actual_name |
| 154 | except Exception as e: |
| 155 | try: |
| 156 | files.delete_dir(abs_path) |
| 157 | except Exception: |
| 158 | pass |
| 159 | raise e |
| 160 | |
| 161 | |
| 162 | def load_project_header(name: str): |
nothing calls this directly
no test coverage detected