Update.
This commit is contained in:
		
							parent
							
								
									1616e4edb9
								
							
						
					
					
						commit
						44ac1d2bfa
					
				| @ -33,7 +33,8 @@ dependencies = [ | |||||||
|     "PyJWT", |     "PyJWT", | ||||||
|     "multiavatar", |     "multiavatar", | ||||||
|     "gitpython", |     "gitpython", | ||||||
|     "uvloop" |     "uvloop", | ||||||
|  |     "humanize" | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
| [tool.setuptools.packages.find] | [tool.setuptools.packages.find] | ||||||
|  | |||||||
| @ -182,8 +182,8 @@ class Application(BaseApplication): | |||||||
|         self.router.add_view("/drive/{drive}.json", DriveView) |         self.router.add_view("/drive/{drive}.json", DriveView) | ||||||
|         self.router.add_view("/stats.json", StatsView) |         self.router.add_view("/stats.json", StatsView) | ||||||
|         self.router.add_view("/user/{user}.html", UserView) |         self.router.add_view("/user/{user}.html", UserView) | ||||||
|         self.router.add_view("/repository/{username}/{repo_name}", RepositoryView) |         self.router.add_view("/repository/{username}/{repository}", RepositoryView) | ||||||
|         self.router.add_view("/repository/{username}/{repo_name}/{rel_path:.*}", RepositoryView) |         self.router.add_view("/repository/{username}/{repository}/{path:.*}", RepositoryView) | ||||||
|         self.router.add_view("/settings/repositories/index.html", RepositoriesIndexView) |         self.router.add_view("/settings/repositories/index.html", RepositoriesIndexView) | ||||||
|         self.router.add_view("/settings/repositories/create.html", RepositoriesCreateView) |         self.router.add_view("/settings/repositories/create.html", RepositoriesCreateView) | ||||||
|         self.router.add_view("/settings/repositories/repository/{name}/update.html", RepositoriesUpdateView) |         self.router.add_view("/settings/repositories/repository/{name}/update.html", RepositoriesUpdateView) | ||||||
|  | |||||||
| @ -1,15 +1,265 @@ | |||||||
| from snek.system.view import BaseView | import os | ||||||
|  | import mimetypes | ||||||
|  | import urllib.parse | ||||||
|  | from pathlib import Path | ||||||
|  | import humanize | ||||||
| from aiohttp import web | from aiohttp import web | ||||||
|  | from snek.system.view import BaseView | ||||||
|  | import asyncio  | ||||||
|  | from git import Repo | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class BareRepoNavigator: | ||||||
|  |     def __init__(self, repo_path): | ||||||
|  |         """Initialize the navigator with a bare repository path.""" | ||||||
|  |         try: | ||||||
|  |             self.repo = Repo(repo_path) | ||||||
|  |             if not self.repo.bare: | ||||||
|  |                 print(f"Error: {repo_path} is not a bare repository.") | ||||||
|  |                 sys.exit(1) | ||||||
|  |         except git.exc.InvalidGitRepositoryError: | ||||||
|  |             print(f"Error: {repo_path} is not a valid Git repository.") | ||||||
|  |             sys.exit(1) | ||||||
|  |         except Exception as e: | ||||||
|  |             print(f"Error opening repository: {str(e)}") | ||||||
|  |             sys.exit(1) | ||||||
|  |              | ||||||
|  |         self.repo_path = repo_path | ||||||
|  |         self.branches = list(self.repo.branches) | ||||||
|  |         self.current_branch = None | ||||||
|  |         self.current_commit = None | ||||||
|  |         self.current_path = "" | ||||||
|  |         self.history = [] | ||||||
|  |          | ||||||
|  |     def get_branches(self): | ||||||
|  |         """Return a list of branch names in the repository.""" | ||||||
|  |         return [branch.name for branch in self.branches] | ||||||
|  |      | ||||||
|  |     def set_branch(self, branch_name): | ||||||
|  |         """Set the current branch.""" | ||||||
|  |         try: | ||||||
|  |             self.current_branch = self.repo.branches[branch_name] | ||||||
|  |             self.current_commit = self.current_branch.commit | ||||||
|  |             self.current_path = "" | ||||||
|  |             self.history = [] | ||||||
|  |             return True | ||||||
|  |         except IndexError: | ||||||
|  |             return False | ||||||
|  |              | ||||||
|  |     def get_commits(self, count=10): | ||||||
|  |         """Get the latest commits on the current branch.""" | ||||||
|  |         if not self.current_branch: | ||||||
|  |             return [] | ||||||
|  |              | ||||||
|  |         commits = [] | ||||||
|  |         for commit in self.repo.iter_commits(self.current_branch, max_count=count): | ||||||
|  |             commits.append({ | ||||||
|  |                 'hash': commit.hexsha, | ||||||
|  |                 'short_hash': commit.hexsha[:7], | ||||||
|  |                 'message': commit.message.strip(), | ||||||
|  |                 'author': commit.author.name, | ||||||
|  |                 'date': datetime.fromtimestamp(commit.committed_date).strftime('%Y-%m-%d %H:%M:%S') | ||||||
|  |             }) | ||||||
|  |         return commits | ||||||
|  |          | ||||||
|  |     def set_commit(self, commit_hash): | ||||||
|  |         """Set the current commit by hash.""" | ||||||
|  |         try: | ||||||
|  |             self.current_commit = self.repo.commit(commit_hash) | ||||||
|  |             self.current_path = "" | ||||||
|  |             self.history = [] | ||||||
|  |             return True | ||||||
|  |         except ValueError: | ||||||
|  |             return False | ||||||
|  |              | ||||||
|  |     def list_directory(self, path=""): | ||||||
|  |         """List the contents of a directory in the current commit.""" | ||||||
|  |         if not self.current_commit: | ||||||
|  |             return {'dirs': [], 'files': []} | ||||||
|  |              | ||||||
|  |         dirs = [] | ||||||
|  |         files = [] | ||||||
|  |          | ||||||
|  |         try: | ||||||
|  |             # Get the tree at the current path | ||||||
|  |             if path: | ||||||
|  |                 tree = self.current_commit.tree[path] | ||||||
|  |                 if not hasattr(tree, 'trees'):  # It's a blob, not a tree | ||||||
|  |                     return {'dirs': [], 'files': [path]} | ||||||
|  |             else: | ||||||
|  |                 tree = self.current_commit.tree | ||||||
|  |                  | ||||||
|  |             # List directories and files | ||||||
|  |             for item in tree: | ||||||
|  |                 if item.type == 'tree': | ||||||
|  |                     item_path = os.path.join(path, item.name) if path else item.name | ||||||
|  |                     dirs.append(item_path) | ||||||
|  |                 elif item.type == 'blob': | ||||||
|  |                     item_path = os.path.join(path, item.name) if path else item.name | ||||||
|  |                     files.append(item_path) | ||||||
|  |                      | ||||||
|  |             dirs.sort() | ||||||
|  |             files.sort() | ||||||
|  |             return {'dirs': dirs, 'files': files} | ||||||
|  |              | ||||||
|  |         except KeyError: | ||||||
|  |             return {'dirs': [], 'files': []} | ||||||
|  |              | ||||||
|  |     def get_file_content(self, file_path): | ||||||
|  |         """Get the content of a file in the current commit.""" | ||||||
|  |         if not self.current_commit: | ||||||
|  |             return None | ||||||
|  |              | ||||||
|  |         try: | ||||||
|  |             blob = self.current_commit.tree[file_path] | ||||||
|  |             return blob.data_stream.read().decode('utf-8', errors='replace') | ||||||
|  |         except (KeyError, UnicodeDecodeError): | ||||||
|  |             try: | ||||||
|  |                 # Try to get as binary if text decoding fails | ||||||
|  |                 blob = self.current_commit.tree[file_path] | ||||||
|  |                 return blob.data_stream.read() | ||||||
|  |             except: | ||||||
|  |                 return None | ||||||
|  |                  | ||||||
|  |     def navigate_to(self, path): | ||||||
|  |         """Navigate to a specific path, updating the current path.""" | ||||||
|  |         if not self.current_commit: | ||||||
|  |             return False | ||||||
|  |              | ||||||
|  |         try: | ||||||
|  |             if path: | ||||||
|  |                 self.current_commit.tree[path]  # Check if path exists | ||||||
|  |             self.history.append(self.current_path) | ||||||
|  |             self.current_path = path | ||||||
|  |             return True | ||||||
|  |         except KeyError: | ||||||
|  |             return False | ||||||
|  |              | ||||||
|  |     def navigate_back(self): | ||||||
|  |         """Navigate back to the previous path.""" | ||||||
|  |         if self.history: | ||||||
|  |             self.current_path = self.history.pop() | ||||||
|  |             return True | ||||||
|  |         return False | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| class RepositoryView(BaseView): | class RepositoryView(BaseView): | ||||||
| 	async def get(A): | 
 | ||||||
| 		G='type';H='name';I='.git';J='username';B=A.request.match_info[J];K=A.request.match_info['repo_name'];C=A.request.match_info.get('rel_path','') |     login_required = True | ||||||
| 		if not B.count('-')==4:E=await A.services.user.get_by_username(B) | 
 | ||||||
| 		else:E=await A.services.user.get(B) |     def checkout_bare_repo(self, bare_repo_path: Path, target_path: Path, ref: str = 'HEAD'): | ||||||
| 		if not E:return web.HTTPNotFound() |         repo = Repo(bare_repo_path) | ||||||
| 		B=E[J];M=await A.services.user.get_repository_path(E['uid']) |         assert repo.bare, "Repository is not bare." | ||||||
| 		if C.endswith(I):C=C[:-4] | 
 | ||||||
| 		L=M.joinpath(K+I) |         commit = repo.commit(ref) | ||||||
| 		if not L.exists():return web.HTTPNotFound() |         tree = commit.tree | ||||||
| 		import os;from git import Repo;N=Repo(L.joinpath(C));F=[];O=[];P=N.head.commit | 
 | ||||||
| 		for D in P.tree.traverse():F.append({H:D.name,'mode':D.mode,G:D.type,'path':D.path,'size':D.size}) |         for blob in tree.traverse(): | ||||||
| 		sorted(F,key=lambda x:x[H]);sorted(F,key=lambda x:x[G],reverse=True);Q=f"{B}/{C}"[:-4];return await A.render_template('repository.html',dict(username=B,repo_name=K,rel_path=C,full_path=Q,files=F,directories=O)) |             target_file = target_path / blob.path | ||||||
|  |                  | ||||||
|  |             target_file.parent.mkdir(parents=True, exist_ok=True) | ||||||
|  |             print(blob.path) | ||||||
|  | 
 | ||||||
|  |             with open(target_file, 'wb') as f: | ||||||
|  |                 f.write(blob.data_stream.read()) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     async def get(self): | ||||||
|  | 
 | ||||||
|  |         base_repo_path = Path("drive/repositories")   | ||||||
|  | 
 | ||||||
|  |         authenticated_user_id = self.session.get("uid") | ||||||
|  | 
 | ||||||
|  |         username = self.request.match_info.get('username') | ||||||
|  |         repo_name = self.request.match_info.get('repository') | ||||||
|  |         rel_path = self.request.match_info.get('path', '') | ||||||
|  |         user = None | ||||||
|  |         if not username.count("-") == 4: | ||||||
|  |             user = await self.app.services.user.get(username=username) | ||||||
|  |             if not user: | ||||||
|  |                 return web.Response(text="404 Not Found", status=404) | ||||||
|  |             username = user["username"] | ||||||
|  |         else: | ||||||
|  |             user = await self.app.services.user.get(uid=username) | ||||||
|  | 
 | ||||||
|  |         repo = await self.app.services.repository.get(name=repo_name, user_uid=user["uid"]) | ||||||
|  |         if not repo: | ||||||
|  |             return web.Response(text="404 Not Found", status=404) | ||||||
|  |         if repo['is_private'] and authenticated_user_id != repo['uid']:   | ||||||
|  |             return web.Response(text="404 Not Found", status=404)  | ||||||
|  | 
 | ||||||
|  |         repo_root_base = (base_repo_path / user['uid'] / (repo_name + ".git")).resolve() | ||||||
|  |         repo_root = (base_repo_path / user['uid'] / repo_name).resolve() | ||||||
|  |         try: | ||||||
|  |             loop = asyncio.get_event_loop() | ||||||
|  |             await loop.run_in_executor(None, | ||||||
|  |                          self.checkout_bare_repo, repo_root_base, repo_root | ||||||
|  |             ) | ||||||
|  |         except: | ||||||
|  |             pass | ||||||
|  |          | ||||||
|  |         if not repo_root.exists() or not repo_root.is_dir(): | ||||||
|  |             return web.Response(text="404 Not Found", status=404) | ||||||
|  | 
 | ||||||
|  |         safe_rel_path = os.path.normpath(rel_path).lstrip(os.sep) | ||||||
|  |         abs_path = (repo_root / safe_rel_path).resolve() | ||||||
|  | 
 | ||||||
|  |         if not abs_path.exists() or not abs_path.is_relative_to(repo_root): | ||||||
|  |             return web.Response(text="404 Not Found", status=404) | ||||||
|  | 
 | ||||||
|  |         if abs_path.is_dir(): | ||||||
|  |             return web.Response(text=self.render_directory(abs_path, username, repo_name, safe_rel_path), content_type='text/html') | ||||||
|  |         else: | ||||||
|  |             return web.Response(text=self.render_file(abs_path), content_type='text/html') | ||||||
|  | 
 | ||||||
|  |     def render_directory(self, abs_path, username, repo_name, safe_rel_path): | ||||||
|  |         entries = sorted(abs_path.iterdir(), key=lambda p: (not p.is_dir(), p.name.lower())) | ||||||
|  |         items = [] | ||||||
|  | 
 | ||||||
|  |         if safe_rel_path: | ||||||
|  |             parent_path = Path(safe_rel_path).parent | ||||||
|  |             parent_link = f"/repository/{username}/{repo_name}/{parent_path}".rstrip('/') | ||||||
|  |             items.append(f'<li><a href="{parent_link}">β¬
οΈ ..</a></li>') | ||||||
|  | 
 | ||||||
|  |         for entry in entries: | ||||||
|  |             link_path = urllib.parse.quote(str(Path(safe_rel_path) / entry.name)) | ||||||
|  |             link = f"/repository/{username}/{repo_name}/{link_path}".rstrip('/') | ||||||
|  |             display = entry.name + ('/' if entry.is_dir() else '') | ||||||
|  |             size = '' if entry.is_dir() else humanize.naturalsize(entry.stat().st_size) | ||||||
|  |             icon = self.get_icon(entry) | ||||||
|  |             items.append(f'<li>{icon} <a href="{link}">{display}</a> {size}</li>') | ||||||
|  | 
 | ||||||
|  |         html = f""" | ||||||
|  |         <html> | ||||||
|  |         <head><title>π {repo_name}/{safe_rel_path}</title></head> | ||||||
|  |         <body> | ||||||
|  |         <h2>π {username}/{repo_name}/{safe_rel_path}</h2> | ||||||
|  |         <ul> | ||||||
|  |             {''.join(items)} | ||||||
|  |         </ul> | ||||||
|  |         </body> | ||||||
|  |         </html> | ||||||
|  |         """ | ||||||
|  |         return html | ||||||
|  | 
 | ||||||
|  |     def render_file(self, abs_path): | ||||||
|  |         try: | ||||||
|  |             with open(abs_path, 'r', encoding='utf-8', errors='ignore') as f: | ||||||
|  |                 content = f.read() | ||||||
|  |             return f"<pre>{content}</pre>" | ||||||
|  |         except Exception as e: | ||||||
|  |             return f"<h1>Error</h1><pre>{e}</pre>" | ||||||
|  | 
 | ||||||
|  |     def get_icon(self, file): | ||||||
|  |         if file.is_dir(): return "π" | ||||||
|  |         mime = mimetypes.guess_type(file.name)[0] or '' | ||||||
|  |         if mime.startswith("image"): return "πΌοΈ" | ||||||
|  |         if mime.startswith("text"): return "π" | ||||||
|  |         if mime.startswith("audio"): return "π΅" | ||||||
|  |         if mime.startswith("video"): return "π¬" | ||||||
|  |         if file.name.endswith(".py"): return "π" | ||||||
|  |         return "π¦" | ||||||
|  | 
 | ||||||
|  | |||||||
		Loadingβ¦
	
		Reference in New Issue
	
	Block a user