#!/usr/bin/env python3 """ Go-Git Auto-Commit Script for LightRAG project. Uses Gitea API directly instead of external Git. """ import requests import os import sys import json import hashlib import base64 from datetime import datetime from pathlib import Path class GoGitAutoCommit: def __init__(self, gitea_url, username, password, repo_owner, repo_name): self.gitea_url = gitea_url.rstrip('/') self.username = username self.password = password self.repo_owner = repo_owner self.repo_name = repo_name self.session = requests.Session() self.session.auth = (username, password) def get_auth_token(self): """Get or create an access token for API calls.""" # Try to get existing tokens tokens_url = f"{self.gitea_url}/api/v1/users/{self.username}/tokens" response = self.session.get(tokens_url) if response.status_code == 200: tokens = response.json() if tokens: return tokens[0]['sha1'] # Create new token token_data = { "name": f"auto-commit-{datetime.now().strftime('%Y%m%d')}", "scopes": ["write:repository", "read:repository"] } response = self.session.post(tokens_url, json=token_data) if response.status_code == 201: return response.json()['sha1'] else: raise Exception(f"Failed to create token: {response.text}") def calculate_file_hash(self, file_path): """Calculate SHA1 hash for file (Go-Git compatible).""" with open(file_path, 'rb') as f: content = f.read() sha1 = hashlib.sha1(content).hexdigest() return sha1, len(content) def create_file_content(self, file_path, relative_path): """Create file content entry for Gitea API.""" sha1, size = self.calculate_file_hash(file_path) with open(file_path, 'rb') as f: content = f.read() encoded = base64.b64encode(content).decode('utf-8') return { "path": relative_path, "sha": sha1, "size": size, "content": encoded, "encoding": "base64" } def get_repo_tree(self, ref="master"): """Get current repository tree.""" url = f"{self.gitea_url}/api/v1/repos/{self.repo_owner}/{self.repo_name}/git/trees/{ref}" response = self.session.get(url) if response.status_code == 200: return response.json() else: # Repository might be empty return {"tree": [], "sha": None} def find_changed_files(self, base_dir="."): """Find changed files by comparing with current tree.""" base_path = Path(base_dir) changed_files = [] # Get current tree current_tree = self.get_repo_tree() current_files = {item['path']: item['sha'] for item in current_tree.get('tree', [])} # Walk through directory for file_path in base_path.rglob('*'): if file_path.is_file(): # Skip .git directory and other ignored files if '.git' in str(file_path): continue relative_path = str(file_path.relative_to(base_path)) # Calculate current hash current_sha1, _ = self.calculate_file_hash(file_path) # Check if file is new or modified if relative_path not in current_files: changed_files.append(("added", relative_path, file_path)) elif current_sha1 != current_files[relative_path]: changed_files.append(("modified", relative_path, file_path)) return changed_files def create_commit(self, message, changed_files, base_dir="."): """Create a commit using Gitea API.""" # Get current commit reference ref_url = f"{self.gitea_url}/api/v1/repos/{self.repo_owner}/{self.repo_name}/git/refs/heads/master" response = self.session.get(ref_url) if response.status_code == 404: # Branch doesn't exist yet (empty repo) parent_sha = None elif response.status_code == 200: parent_sha = response.json()['object']['sha'] else: raise Exception(f"Failed to get ref: {response.text}") # Create tree with changed files tree_items = [] for change_type, relative_path, file_path in changed_files: if change_type in ["added", "modified"]: file_content = self.create_file_content(file_path, relative_path) tree_items.append({ "path": relative_path, "mode": "100644", # Regular file "type": "blob", "sha": file_content["sha"] }) # Create tree tree_data = { "base_tree": parent_sha, "tree": tree_items } tree_url = f"{self.gitea_url}/api/v1/repos/{self.repo_owner}/{self.repo_name}/git/trees" response = self.session.post(tree_url, json=tree_data) if response.status_code != 201: raise Exception(f"Failed to create tree: {response.text}") tree_sha = response.json()['sha'] # Create commit commit_data = { "message": message, "tree": tree_sha, "parents": [parent_sha] if parent_sha else [] } commit_url = f"{self.gitea_url}/api/v1/repos/{self.repo_owner}/{self.repo_name}/git/commits" response = self.session.post(commit_url, json=commit_data) if response.status_code != 201: raise Exception(f"Failed to create commit: {response.text}") commit_sha = response.json()['sha'] # Update reference ref_data = { "sha": commit_sha, "force": False } response = self.session.patch(ref_url, json=ref_data) if response.status_code != 200: # Try to create the reference ref_url = f"{self.gitea_url}/api/v1/repos/{self.repo_owner}/{self.repo_name}/git/refs" ref_data = { "ref": "refs/heads/master", "sha": commit_sha } response = self.session.post(ref_url, json=ref_data) if response.status_code != 201: raise Exception(f"Failed to update ref: {response.text}") return commit_sha def auto_commit(self, message=None, base_dir="."): """Main auto-commit function using Go-Git API.""" if not message: timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") message = f"Go-Git Auto-Commit: {timestamp}" print(f"Go-Git Auto-Commit starting with message: {message}") print("=" * 60) # Find changed files print("1. Scanning for changed files...") changed_files = self.find_changed_files(base_dir) if not changed_files: print("No changes detected.") return True print(f"Found {len(changed_files)} changed files:") for change_type, relative_path, _ in changed_files[:10]: # Show first 10 print(f" {change_type}: {relative_path}") if len(changed_files) > 10: print(f" ... and {len(changed_files) - 10} more") # Create commit print(f"\n2. Creating commit: '{message}'") try: commit_sha = self.create_commit(message, changed_files, base_dir) print(f"Commit created successfully: {commit_sha}") # Show commit URL commit_url = f"{self.gitea_url}/{self.repo_owner}/{self.repo_name}/commit/{commit_sha}" print(f"Commit URL: {commit_url}") return True except Exception as e: print(f"Error creating commit: {e}") return False def main(): # Configuration GITEA_URL = "https://git.mtrcompute.com" USERNAME = "jleu3482" PASSWORD = "jleu1212" REPO_OWNER = "jleu3482" REPO_NAME = "railseek6" # Get commit message from command line if len(sys.argv) > 1: message = sys.argv[1] else: timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") message = f"Go-Git Auto-Commit: {timestamp}" print("Go-Git Auto-Commit Script for LightRAG") print("=" * 60) # Initialize Go-Git client gogit = GoGitAutoCommit(GITEA_URL, USERNAME, PASSWORD, REPO_OWNER, REPO_NAME) # Run auto-commit success = gogit.auto_commit(message) if success: print("\n" + "=" * 60) print("Go-Git auto-commit completed successfully!") sys.exit(0) else: print("\n" + "=" * 60) print("Go-Git auto-commit failed!") sys.exit(1) if __name__ == "__main__": main()