Patch
This commit is contained in:
		
							parent
							
								
									ee40c905d4
								
							
						
					
					
						commit
						a5aac9a337
					
				| @ -31,7 +31,8 @@ dependencies = [ | ||||
|     "emoji", | ||||
|     "aiofiles", | ||||
|     "PyJWT", | ||||
|     "multiavatar" | ||||
|     "multiavatar", | ||||
|     "gitpython", | ||||
| ] | ||||
| 
 | ||||
| [tool.setuptools.packages.find] | ||||
|  | ||||
| @ -52,6 +52,7 @@ from snek.view.upload import UploadView | ||||
| from snek.view.user import UserView | ||||
| from snek.view.web import WebView | ||||
| from snek.webdav import WebdavApplication | ||||
| from snek.sgit import GitApplication | ||||
| 
 | ||||
| SESSION_KEY = b"c79a0c5fda4b424189c427d28c9f7c34" | ||||
| 
 | ||||
| @ -184,12 +185,9 @@ class Application(BaseApplication): | ||||
|         self.router.add_view("/settings/repositories/repository/{name}/update.html", RepositoriesUpdateView) | ||||
|         self.router.add_view("/settings/repositories/respository/{name}/delete.html", RepositoriesDeleteView) | ||||
|         self.webdav = WebdavApplication(self) | ||||
|         self.git = GitApplication(self) | ||||
|         self.add_subapp("/webdav", self.webdav) | ||||
| 
 | ||||
|         self.add_subapp( | ||||
|             "/docs", | ||||
|             DocsApplication(path=pathlib.Path(__file__).parent.joinpath("docs")), | ||||
|         ) | ||||
|         self.add_subapp("/git",self.git) | ||||
|          | ||||
|         #self.router.add_get("/{file_path:.*}", self.static_handler) | ||||
|      | ||||
|  | ||||
| @ -7,7 +7,7 @@ class RepositoryService(BaseService): | ||||
|     async def exists(self, user_uid, name, **kwargs): | ||||
|         kwargs["user_uid"] = user_uid | ||||
|         kwargs["name"] = name | ||||
|         return await self.exists(**kwargs) | ||||
|         return await super().exists(**kwargs) | ||||
| 
 | ||||
|     async def init(self, user_uid, name): | ||||
|         repository_path = await self.services.user.get_repository_path(user_uid) | ||||
| @ -21,16 +21,14 @@ class RepositoryService(BaseService): | ||||
|             stderr=asyncio.subprocess.PIPE | ||||
|         ) | ||||
|         stdout, stderr = await process.communicate() | ||||
|         if process.returncode == 0: | ||||
|             print(f"Bare Git repository created at: {repo_path}") | ||||
|         else: | ||||
|             print(f"Error creating repository: {stderr.decode().strip()}") | ||||
|         return process.returncode == 0 | ||||
| 
 | ||||
|     async def create(self, user_uid, name,is_private=False): | ||||
|         if await self.exists(user_uid=user_uid, name=name): | ||||
|             return False  | ||||
| 
 | ||||
| 
 | ||||
|         if not await self.init(user_uid=user_uid, name=name): | ||||
|             return False | ||||
| 
 | ||||
|         model = await self.new() | ||||
|         model["user_uid"] = user_uid | ||||
|  | ||||
| @ -43,10 +43,7 @@ class UserService(BaseService): | ||||
|         return self.mapper.get_admin_uids() | ||||
| 
 | ||||
|     async def get_repository_path(self, user_uid): | ||||
|         path = pathlib.Path(f"./drive/repositories/{user_uid}") | ||||
|         if not path.exists(): | ||||
|             return None | ||||
|         return path | ||||
|         return pathlib.Path(f"./drive/repositories/{user_uid}") | ||||
| 
 | ||||
|     async def get_static_path(self, user_uid): | ||||
|         path = pathlib.Path(f"./drive/{user_uid}/snek/static") | ||||
|  | ||||
							
								
								
									
										472
									
								
								src/snek/sgit.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										472
									
								
								src/snek/sgit.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,472 @@ | ||||
| import os | ||||
| import aiohttp | ||||
| from aiohttp import web | ||||
| import git | ||||
| import shutil | ||||
| import json | ||||
| import tempfile | ||||
| import asyncio | ||||
| import logging | ||||
| import base64 | ||||
| import pathlib | ||||
| logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s') | ||||
| logger = logging.getLogger('git_server') | ||||
| 
 | ||||
| class GitApplication(web.Application): | ||||
|     def __init__(self, parent=None): | ||||
|         self.parent = parent | ||||
|         super().__init__(client_max_size=100*1024*1024) | ||||
|         self.REPO_DIR = "drive/repositories/3177f85e-dbb3-4406-993e-3d3748fea545" | ||||
|         self.USERS = { | ||||
|             'x': 'x', | ||||
|             'bob': 'bobpass', | ||||
|         } | ||||
|         self.add_routes([ | ||||
|             web.post('/create/{repo_name}', self.create_repository), | ||||
|             web.delete('/delete/{repo_name}', self.delete_repository), | ||||
|             web.get('/clone/{repo_name}', self.clone_repository), | ||||
|             web.post('/push/{repo_name}', self.push_repository), | ||||
|             web.post('/pull/{repo_name}', self.pull_repository), | ||||
|             web.get('/status/{repo_name}', self.status_repository), | ||||
|             web.get('/list', self.list_repositories), | ||||
|             web.get('/branches/{repo_name}', self.list_branches), | ||||
|             web.post('/branches/{repo_name}', self.create_branch), | ||||
|             web.get('/log/{repo_name}', self.commit_log), | ||||
|             web.get('/file/{repo_name}/{file_path:.*}', self.file_content), | ||||
|             web.get('/{path:.+}/info/refs', self.git_smart_http), | ||||
|             web.post('/{path:.+}/git-upload-pack', self.git_smart_http), | ||||
|             web.post('/{path:.+}/git-receive-pack', self.git_smart_http), | ||||
|             web.get('/{repo_name}.git/info/refs', self.git_smart_http), | ||||
|             web.post('/{repo_name}.git/git-upload-pack', self.git_smart_http), | ||||
|             web.post('/{repo_name}.git/git-receive-pack', self.git_smart_http), | ||||
|         ]) | ||||
| 
 | ||||
|     async def check_basic_auth(self, request): | ||||
|         # For now, always return the fixed user and path | ||||
|         return "retoor", pathlib.Path(self.REPO_DIR) | ||||
| 
 | ||||
|     @staticmethod | ||||
|     def require_auth(handler): | ||||
|         async def wrapped(self, request, *args, **kwargs): | ||||
|             username, repository_path = await self.check_basic_auth(request) | ||||
|             if not username or not repository_path: | ||||
|                 return web.Response(status=401, headers={'WWW-Authenticate': 'Basic'}, text='Authentication required') | ||||
|             request['username'] = username | ||||
|             request['repository_path'] = repository_path | ||||
|             return await handler(self, request, *args, **kwargs) | ||||
|         return wrapped | ||||
| 
 | ||||
|     def repo_path(self, repository_path, repo_name): | ||||
|         return repository_path.joinpath(repo_name + '.git') | ||||
| 
 | ||||
|     def check_repo_exists(self, repository_path, repo_name): | ||||
|         repo_dir = self.repo_path(repository_path, repo_name) | ||||
|         if not os.path.exists(repo_dir): | ||||
|             return web.Response(text="Repository not found", status=404) | ||||
|         return None | ||||
| 
 | ||||
|     @require_auth | ||||
|     async def create_repository(self, request): | ||||
|         username = request['username'] | ||||
|         repo_name = request.match_info['repo_name'] | ||||
|         repository_path = request['repository_path'] | ||||
|         if not repo_name or '/' in repo_name or '..' in repo_name: | ||||
|             return web.Response(text="Invalid repository name", status=400) | ||||
|         repo_dir = self.repo_path(repository_path, repo_name) | ||||
|         if os.path.exists(repo_dir): | ||||
|             return web.Response(text="Repository already exists", status=400) | ||||
|         try: | ||||
|             git.Repo.init(repo_dir, bare=True) | ||||
|             logger.info(f"Created repository: {repo_name} for user {username}") | ||||
|             return web.Response(text=f"Created repository {repo_name}") | ||||
|         except Exception as e: | ||||
|             logger.error(f"Error creating repository {repo_name}: {str(e)}") | ||||
|             return web.Response(text=f"Error creating repository: {str(e)}", status=500) | ||||
| 
 | ||||
|     @require_auth | ||||
|     async def delete_repository(self, request): | ||||
|         username = request['username'] | ||||
|         repo_name = request.match_info['repo_name'] | ||||
|         repository_path = request['repository_path'] | ||||
|         error_response = self.check_repo_exists(repository_path, repo_name) | ||||
|         if error_response: | ||||
|             return error_response | ||||
|         try: | ||||
|             shutil.rmtree(self.repo_path(repository_path, repo_name)) | ||||
|             logger.info(f"Deleted repository: {repo_name} for user {username}") | ||||
|             return web.Response(text=f"Deleted repository {repo_name}") | ||||
|         except Exception as e: | ||||
|             logger.error(f"Error deleting repository {repo_name}: {str(e)}") | ||||
|             return web.Response(text=f"Error deleting repository: {str(e)}", status=500) | ||||
| 
 | ||||
|     @require_auth | ||||
|     async def clone_repository(self, request): | ||||
|         username = request['username'] | ||||
|         repo_name = request.match_info['repo_name'] | ||||
|         repository_path = request['repository_path'] | ||||
|         error_response = self.check_repo_exists(repository_path, repo_name) | ||||
|         if error_response: | ||||
|             return error_response | ||||
|         host = request.host | ||||
|         clone_url = f"http://{host}/{repo_name}.git" | ||||
|         response_data = { | ||||
|             "repository": repo_name, | ||||
|             "clone_command": f"git clone {clone_url}", | ||||
|             "clone_url": clone_url | ||||
|         } | ||||
|         return web.json_response(response_data) | ||||
| 
 | ||||
|     @require_auth | ||||
|     async def push_repository(self, request): | ||||
|         username = request['username'] | ||||
|         repo_name = request.match_info['repo_name'] | ||||
|         repository_path = request['repository_path'] | ||||
|         error_response = self.check_repo_exists(repository_path, repo_name) | ||||
|         if error_response: | ||||
|             return error_response | ||||
|         try: | ||||
|             data = await request.json() | ||||
|         except json.JSONDecodeError: | ||||
|             return web.Response(text="Invalid JSON data", status=400) | ||||
|         commit_message = data.get('commit_message', 'Update from server') | ||||
|         branch = data.get('branch', 'main') | ||||
|         changes = data.get('changes', []) | ||||
|         if not changes: | ||||
|             return web.Response(text="No changes provided", status=400) | ||||
|         with tempfile.TemporaryDirectory() as temp_dir: | ||||
|             temp_repo = git.Repo.clone_from(self.repo_path(repository_path, repo_name), temp_dir) | ||||
|             for change in changes: | ||||
|                 file_path = os.path.join(temp_dir, change.get('file', '')) | ||||
|                 content = change.get('content', '') | ||||
|                 os.makedirs(os.path.dirname(file_path), exist_ok=True) | ||||
|                 with open(file_path, 'w') as f: | ||||
|                     f.write(content) | ||||
|             temp_repo.git.add(A=True) | ||||
|             if not temp_repo.config_reader().has_section('user'): | ||||
|                 temp_repo.config_writer().set_value("user", "name", "Git Server").release() | ||||
|                 temp_repo.config_writer().set_value("user", "email", "git@server.local").release() | ||||
|             temp_repo.index.commit(commit_message) | ||||
|             origin = temp_repo.remote('origin') | ||||
|             origin.push(refspec=f"{branch}:{branch}") | ||||
|         logger.info(f"Pushed to repository: {repo_name} for user {username}") | ||||
|         return web.Response(text=f"Successfully pushed changes to {repo_name}") | ||||
| 
 | ||||
|     @require_auth | ||||
|     async def pull_repository(self, request): | ||||
|         username = request['username'] | ||||
|         repo_name = request.match_info['repo_name'] | ||||
|         repository_path = request['repository_path'] | ||||
|         error_response = self.check_repo_exists(repository_path, repo_name) | ||||
|         if error_response: | ||||
|             return error_response | ||||
|         try: | ||||
|             data = await request.json() | ||||
|         except json.JSONDecodeError: | ||||
|             data = {} | ||||
|         remote_url = data.get('remote_url') | ||||
|         branch = data.get('branch', 'main') | ||||
|         if not remote_url: | ||||
|             return web.Response(text="Remote URL is required", status=400) | ||||
|         with tempfile.TemporaryDirectory() as temp_dir: | ||||
|             try: | ||||
|                 local_repo = git.Repo.clone_from(self.repo_path(repository_path, repo_name), temp_dir) | ||||
|                 remote_name = "pull_source" | ||||
|                 try: | ||||
|                     remote = local_repo.create_remote(remote_name, remote_url) | ||||
|                 except git.GitCommandError: | ||||
|                     remote = local_repo.remote(remote_name) | ||||
|                     remote.set_url(remote_url) | ||||
|                 remote.fetch() | ||||
|                 local_repo.git.merge(f"{remote_name}/{branch}") | ||||
|                 origin = local_repo.remote('origin') | ||||
|                 origin.push() | ||||
|                 logger.info(f"Pulled to repository {repo_name} from {remote_url} for user {username}") | ||||
|                 return web.Response(text=f"Successfully pulled changes from {remote_url} to {repo_name}") | ||||
|             except Exception as e: | ||||
|                 logger.error(f"Error pulling to {repo_name}: {str(e)}") | ||||
|                 return web.Response(text=f"Error pulling changes: {str(e)}", status=500) | ||||
| 
 | ||||
|     @require_auth | ||||
|     async def status_repository(self, request): | ||||
|         username = request['username'] | ||||
|         repo_name = request.match_info['repo_name'] | ||||
|         repository_path = request['repository_path'] | ||||
|         error_response = self.check_repo_exists(repository_path, repo_name) | ||||
|         if error_response: | ||||
|             return error_response | ||||
|         with tempfile.TemporaryDirectory() as temp_dir: | ||||
|             try: | ||||
|                 temp_repo = git.Repo.clone_from(self.repo_path(repository_path, repo_name), temp_dir) | ||||
|                 branches = [b.name for b in temp_repo.branches] | ||||
|                 active_branch = temp_repo.active_branch.name | ||||
|                 commits = [] | ||||
|                 for commit in list(temp_repo.iter_commits(max_count=5)): | ||||
|                     commits.append({ | ||||
|                         "id": commit.hexsha, | ||||
|                         "author": f"{commit.author.name} <{commit.author.email}>", | ||||
|                         "date": commit.committed_datetime.isoformat(), | ||||
|                         "message": commit.message | ||||
|                     }) | ||||
|                 files = [] | ||||
|                 for root, dirs, filenames in os.walk(temp_dir): | ||||
|                     if '.git' in root: | ||||
|                         continue | ||||
|                     for filename in filenames: | ||||
|                         full_path = os.path.join(root, filename) | ||||
|                         rel_path = os.path.relpath(full_path, temp_dir) | ||||
|                         files.append(rel_path) | ||||
|                 status_info = { | ||||
|                     "repository": repo_name, | ||||
|                     "branches": branches, | ||||
|                     "active_branch": active_branch, | ||||
|                     "recent_commits": commits, | ||||
|                     "files": files | ||||
|                 } | ||||
|                 return web.json_response(status_info) | ||||
|             except Exception as e: | ||||
|                 logger.error(f"Error getting status for {repo_name}: {str(e)}") | ||||
|                 return web.Response(text=f"Error getting repository status: {str(e)}", status=500) | ||||
| 
 | ||||
|     @require_auth | ||||
|     async def list_repositories(self, request): | ||||
|         username = request['username'] | ||||
|         try: | ||||
|             repos = [] | ||||
|             user_dir = self.REPO_DIR | ||||
|             if os.path.exists(user_dir): | ||||
|                 for item in os.listdir(user_dir): | ||||
|                     item_path = os.path.join(user_dir, item) | ||||
|                     if os.path.isdir(item_path) and item.endswith('.git'): | ||||
|                         repos.append(item[:-4]) | ||||
|             if request.query.get('format') == 'json': | ||||
|                 return web.json_response({"repositories": repos}) | ||||
|             else: | ||||
|                 return web.Response(text="\n".join(repos) if repos else "No repositories found") | ||||
|         except Exception as e: | ||||
|             logger.error(f"Error listing repositories: {str(e)}") | ||||
|             return web.Response(text=f"Error listing repositories: {str(e)}", status=500) | ||||
| 
 | ||||
|     @require_auth | ||||
|     async def list_branches(self, request): | ||||
|         username = request['username'] | ||||
|         repo_name = request.match_info['repo_name'] | ||||
|         repository_path = request['repository_path'] | ||||
|         error_response = self.check_repo_exists(repository_path, repo_name) | ||||
|         if error_response: | ||||
|             return error_response | ||||
|         with tempfile.TemporaryDirectory() as temp_dir: | ||||
|             temp_repo = git.Repo.clone_from(self.repo_path(repository_path, repo_name), temp_dir) | ||||
|             branches = [b.name for b in temp_repo.branches] | ||||
|             return web.json_response({"branches": branches}) | ||||
| 
 | ||||
|     @require_auth | ||||
|     async def create_branch(self, request): | ||||
|         username = request['username'] | ||||
|         repo_name = request.match_info['repo_name'] | ||||
|         repository_path = request['repository_path'] | ||||
|         error_response = self.check_repo_exists(repository_path, repo_name) | ||||
|         if error_response: | ||||
|             return error_response | ||||
|         try: | ||||
|             data = await request.json() | ||||
|         except json.JSONDecodeError: | ||||
|             return web.Response(text="Invalid JSON data", status=400) | ||||
|         branch_name = data.get('branch_name') | ||||
|         start_point = data.get('start_point', 'HEAD') | ||||
|         if not branch_name: | ||||
|             return web.Response(text="Branch name is required", status=400) | ||||
|         with tempfile.TemporaryDirectory() as temp_dir: | ||||
|             try: | ||||
|                 temp_repo = git.Repo.clone_from(self.repo_path(repository_path, repo_name), temp_dir) | ||||
|                 temp_repo.git.branch(branch_name, start_point) | ||||
|                 temp_repo.git.push('origin', branch_name) | ||||
|                 logger.info(f"Created branch {branch_name} in repository {repo_name} for user {username}") | ||||
|                 return web.Response(text=f"Created branch {branch_name}") | ||||
|             except Exception as e: | ||||
|                 logger.error(f"Error creating branch {branch_name} in {repo_name}: {str(e)}") | ||||
|                 return web.Response(text=f"Error creating branch: {str(e)}", status=500) | ||||
| 
 | ||||
|     @require_auth | ||||
|     async def commit_log(self, request): | ||||
|         username = request['username'] | ||||
|         repo_name = request.match_info['repo_name'] | ||||
|         repository_path = request['repository_path'] | ||||
|         error_response = self.check_repo_exists(repository_path, repo_name) | ||||
|         if error_response: | ||||
|             return error_response | ||||
|         try: | ||||
|             limit = int(request.query.get('limit', 10)) | ||||
|             branch = request.query.get('branch', 'main') | ||||
|         except ValueError: | ||||
|             return web.Response(text="Invalid limit parameter", status=400) | ||||
|         with tempfile.TemporaryDirectory() as temp_dir: | ||||
|             try: | ||||
|                 temp_repo = git.Repo.clone_from(self.repo_path(repository_path, repo_name), temp_dir) | ||||
|                 commits = [] | ||||
|                 try: | ||||
|                     for commit in list(temp_repo.iter_commits(branch, max_count=limit)): | ||||
|                         commits.append({ | ||||
|                             "id": commit.hexsha, | ||||
|                             "short_id": commit.hexsha[:7], | ||||
|                             "author": f"{commit.author.name} <{commit.author.email}>", | ||||
|                             "date": commit.committed_datetime.isoformat(), | ||||
|                             "message": commit.message.strip() | ||||
|                         }) | ||||
|                 except git.GitCommandError as e: | ||||
|                     if "unknown revision or path" in str(e): | ||||
|                         commits = [] | ||||
|                     else: | ||||
|                         raise | ||||
|                 return web.json_response({ | ||||
|                     "repository": repo_name, | ||||
|                     "branch": branch, | ||||
|                     "commits": commits | ||||
|                 }) | ||||
|             except Exception as e: | ||||
|                 logger.error(f"Error getting commit log for {repo_name}: {str(e)}") | ||||
|                 return web.Response(text=f"Error getting commit log: {str(e)}", status=500) | ||||
| 
 | ||||
|     @require_auth | ||||
|     async def file_content(self, request): | ||||
|         username = request['username'] | ||||
|         repo_name = request.match_info['repo_name'] | ||||
|         file_path = request.match_info.get('file_path', '') | ||||
|         branch = request.query.get('branch', 'main') | ||||
|         repository_path = request['repository_path'] | ||||
|         error_response = self.check_repo_exists(repository_path, repo_name) | ||||
|         if error_response: | ||||
|             return error_response | ||||
|         with tempfile.TemporaryDirectory() as temp_dir: | ||||
|             try: | ||||
|                 temp_repo = git.Repo.clone_from(self.repo_path(repository_path, repo_name), temp_dir) | ||||
|                 try: | ||||
|                     temp_repo.git.checkout(branch) | ||||
|                 except git.GitCommandError: | ||||
|                     return web.Response(text=f"Branch '{branch}' not found", status=404) | ||||
|                 file_full_path = os.path.join(temp_dir, file_path) | ||||
|                 if not os.path.exists(file_full_path): | ||||
|                     return web.Response(text=f"File '{file_path}' not found", status=404) | ||||
|                 if os.path.isdir(file_full_path): | ||||
|                     files = os.listdir(file_full_path) | ||||
|                     return web.json_response({ | ||||
|                         "repository": repo_name, | ||||
|                         "path": file_path, | ||||
|                         "type": "directory", | ||||
|                         "contents": files | ||||
|                     }) | ||||
|                 else: | ||||
|                     try: | ||||
|                         with open(file_full_path, 'r') as f: | ||||
|                             content = f.read() | ||||
|                         return web.Response(text=content) | ||||
|                     except UnicodeDecodeError: | ||||
|                         return web.Response(text=f"Cannot display binary file content for '{file_path}'", status=400) | ||||
|             except Exception as e: | ||||
|                 logger.error(f"Error getting file content from {repo_name}: {str(e)}") | ||||
|                 return web.Response(text=f"Error getting file content: {str(e)}", status=500) | ||||
| 
 | ||||
|     @require_auth | ||||
|     async def git_smart_http(self, request): | ||||
|         username = request['username'] | ||||
|         repository_path = request['repository_path'] | ||||
|         path = request.path | ||||
|         async def get_repository_path(): | ||||
|             req_path = path.lstrip('/') | ||||
|             if req_path.endswith('/info/refs'): | ||||
|                 repo_name = req_path[:-len('/info/refs')] | ||||
|             elif req_path.endswith('/git-upload-pack'): | ||||
|                 repo_name = req_path[:-len('/git-upload-pack')] | ||||
|             elif req_path.endswith('/git-receive-pack'): | ||||
|                 repo_name = req_path[:-len('/git-receive-pack')] | ||||
|             else: | ||||
|                 repo_name = req_path | ||||
|             if repo_name.endswith('.git'): | ||||
|                 repo_name = repo_name[:-4] | ||||
|             repo_name = repo_name.lstrip('git/') | ||||
|             repo_dir = repository_path.joinpath(repo_name + '.git') | ||||
|             logger.info(f"Resolved repo path: {repo_dir}") | ||||
|             return repo_dir | ||||
|         async def handle_info_refs(service): | ||||
|             repo_path = await get_repository_path() | ||||
|          | ||||
|             logger.info(f"handle_info_refs: {repo_path}") | ||||
|             if not os.path.exists(repo_path): | ||||
|                 return web.Response(text="Repository not found", status=404) | ||||
|             cmd = [service, '--stateless-rpc', '--advertise-refs', str(repo_path)] | ||||
|             try: | ||||
|                 process = await asyncio.create_subprocess_exec( | ||||
|                     *cmd, | ||||
|                     stdout=asyncio.subprocess.PIPE, | ||||
|                     stderr=asyncio.subprocess.PIPE | ||||
|                 ) | ||||
|                 stdout, stderr = await process.communicate() | ||||
|                 if process.returncode != 0: | ||||
|                     logger.error(f"Git command failed: {stderr.decode()}") | ||||
|                     return web.Response(text=f"Git error: {stderr.decode()}", status=500) | ||||
|                 response = web.StreamResponse( | ||||
|                     status=200, | ||||
|                     reason='OK', | ||||
|                     headers={ | ||||
|                         'Content-Type': f'application/x-{service}-advertisement', | ||||
|                         'Cache-Control': 'no-cache' | ||||
|                     } | ||||
|                 ) | ||||
|                 await response.prepare(request) | ||||
|                 packet = f"# service={service}\n" | ||||
|                 length = len(packet) + 4 | ||||
|                 header = f"{length:04x}" | ||||
|                 await response.write(f"{header}{packet}0000".encode()) | ||||
|                 await response.write(stdout) | ||||
|                 return response | ||||
|             except Exception as e: | ||||
|                 logger.error(f"Error handling info/refs: {str(e)}") | ||||
|                 return web.Response(text=f"Server error: {str(e)}", status=500) | ||||
|         async def handle_service_rpc(service): | ||||
|             repo_path = await get_repository_path() | ||||
|             logger.info(f"handle_service_rpc: {repo_path}") | ||||
|             if not os.path.exists(repo_path): | ||||
|                 return web.Response(text="Repository not found", status=404) | ||||
|             if not request.headers.get('Content-Type') == f'application/x-{service}-request': | ||||
|                 return web.Response(text="Invalid Content-Type", status=403) | ||||
|             body = await request.read() | ||||
|             cmd = [service, '--stateless-rpc', str(repo_path)] | ||||
|             try: | ||||
|                 process = await asyncio.create_subprocess_exec( | ||||
|                     *cmd, | ||||
|                     stdin=asyncio.subprocess.PIPE, | ||||
|                     stdout=asyncio.subprocess.PIPE, | ||||
|                     stderr=asyncio.subprocess.PIPE | ||||
|                 ) | ||||
|                 stdout, stderr = await process.communicate(input=body) | ||||
|                 if process.returncode != 0: | ||||
|                     logger.error(f"Git command failed: {stderr.decode()}") | ||||
|                     return web.Response(text=f"Git error: {stderr.decode()}", status=500) | ||||
|                 return web.Response( | ||||
|                     body=stdout, | ||||
|                     content_type=f'application/x-{service}-result' | ||||
|                 ) | ||||
|             except Exception as e: | ||||
|                 logger.error(f"Error handling service RPC: {str(e)}") | ||||
|                 return web.Response(text=f"Server error: {str(e)}", status=500) | ||||
|         if request.method == 'GET' and path.endswith('/info/refs'): | ||||
|             service = request.query.get('service') | ||||
|             if service in ('git-upload-pack', 'git-receive-pack'): | ||||
|                 return await handle_info_refs(service) | ||||
|             else: | ||||
|                 return web.Response(text="Smart HTTP requires service parameter", status=400) | ||||
|         elif request.method == 'POST' and '/git-upload-pack' in path: | ||||
|             return await handle_service_rpc('git-upload-pack') | ||||
|         elif request.method == 'POST' and '/git-receive-pack' in path: | ||||
|             return await handle_service_rpc('git-receive-pack') | ||||
|         return web.Response(text="Not found", status=404) | ||||
| 
 | ||||
| if __name__ == '__main__': | ||||
|     try: | ||||
|         import uvloop | ||||
|         asyncio.set_event_loop_policy(uvloop.EventLoopPolicy()) | ||||
|         logger.info("Using uvloop for improved performance") | ||||
|     except ImportError: | ||||
|         logger.info("uvloop not available, using standard event loop") | ||||
|     app = GitApplication() | ||||
|     logger.info("Starting Git server on port 8080") | ||||
|     web.run_app(app, port=8080) | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user