From 492864d5aece7a54d762ada704d82e17454fef60 Mon Sep 17 00:00:00 2001 From: KaySar12 Date: Thu, 15 May 2025 18:08:59 +0700 Subject: [PATCH] refactor(interpreter): enhance submodule handling and simplify credential usage --- registry.mk | 2 - scripts/interpreter/gen_commit.py | 194 +++++++++++++++++++++--------- 2 files changed, 138 insertions(+), 58 deletions(-) diff --git a/registry.mk b/registry.mk index 3985d75..88dfc11 100644 --- a/registry.mk +++ b/registry.mk @@ -2,8 +2,6 @@ REGISTRY_PATH = $(shell pwd)/registry REGISTRY_REPO = git.nextzenos.com/CDN/bifrost-registry.git PYTHON = $(REGISTRY_PATH)/venv/bin/python update_registry: package_registry upload_registry clean_registry - ( cd $(REGISTRY_PATH) && \ - $(PYTHON) $(REGISTRY_PATH)/scripts/interpreter/gen_commit.py $(GIT_USER) $(GIT_PASS) $(REGISTRY_REPO) $(REGISTRY_PATH) ) package_registry: (cd $(REGISTRY_PATH) && \ diff --git a/scripts/interpreter/gen_commit.py b/scripts/interpreter/gen_commit.py index 91481c1..e7b91e6 100755 --- a/scripts/interpreter/gen_commit.py +++ b/scripts/interpreter/gen_commit.py @@ -1,96 +1,178 @@ +import asyncio import os import sys from dotenv import load_dotenv from interpreter import OpenInterpreter -load_dotenv() # Load environment variables from .env +load_dotenv() def get_git_credentials(): - """Retrieve Git credentials and project path from command-line arguments.""" - if len(sys.argv) != 5: - print( - "Usage: python gen_commit.py " - ) + if len(sys.argv) != 4: + print("Usage: python gen_commit.py ") sys.exit(1) - return sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4] + return sys.argv[1], sys.argv[2], sys.argv[3] + + +def get_git_diff(project_path): + """Run git diff and return the output.""" + result = os.popen(f'cd "{project_path}" && git diff').read() + return result.strip() def gen_commit(project_path): - """Use OpenInterpreter to generate a commit message based on git diff.""" - + """Generate commit message using OpenInterpreter based on git diff output.""" agent = OpenInterpreter() agent.llm.model = "gpt-4o" agent.auto_run = True - # Read both convention and git commit files - convention_path = os.path.join( - os.path.dirname(__file__), "resources/commit_convention.md" - ) - git_commit_path = os.path.join(os.path.dirname(__file__), "resources/git_commit.md") - + convention_path = os.path.join(os.path.dirname(__file__), "resources/commit_convention.md") with open(convention_path, "r") as f: convention_content = f.read() - with open(git_commit_path, "r") as f: - git_commit_content = f.read() agent.system_message = f""" - Your name is Bifrost. - You are a helpful assistant that generates commit messages based on uncommitted code. +You are a helpful assistant that generates concise, conventional commit messages based on code diffs. - Follow these rules: - 1. Use English. - 2. Be concise and follow this format: - {convention_content} - - 3. Use these git commands to analyze changes as you see fit: - {git_commit_content} - - 4. Only return the commit message. Do not add explanation or extra output. - """ +Guidelines: +- Use English. +- Do not include explanations—only output commit message in under 100 characters. +- Follow this convention: +{convention_content} +""" + + diff_output = get_git_diff(project_path) + if not diff_output: + return "chore: no changes to commit" prompt = f""" - Generate a commit message based on the following changes in the project directory: +Analyze the following git diff and generate a commit message: - First, change directory: - cd {project_path} - Then check the uncommitted changes. - - Then generate a commit message according to the changes. - """ +{diff_output} +""" response = agent.chat(prompt) - - # Extract just the content from the response - if isinstance(response, list) and len(response) > 0: - if isinstance(response[-1], dict) and "content" in response[-1]: - return response[-1]["content"].strip() - + if isinstance(response, list) and response and isinstance(response[-1], dict) and "content" in response[-1]: + return response[-1]["content"].strip() return str(response).strip() -def push_code(): - """Pull, commit, and push code with AI-generated message.""" - git_user, git_pass, git_repo, project_path = get_git_credentials() +async def get_submodule_info(): + proc = await asyncio.create_subprocess_exec( + "git", "config", "--file", ".gitmodules", "--get-regexp", r"^submodule\..*\.path$", + stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE, + ) + stdout, stderr = await proc.communicate() - # Change to project path + if stderr: + print(f"Error getting submodule paths:\n{stderr.decode()}") + return {} + + submodule_info = {} + for line in stdout.decode().splitlines(): + parts = line.split() + if len(parts) == 2: + name = parts[0].split('.')[1] + path = parts[1] + abs_path = os.path.abspath(path) + submodule_info[abs_path] = name + return submodule_info + + +async def commit_and_push_submodules(): + print("Checking for submodule changes...") + submodule_info = await get_submodule_info() + + proc = await asyncio.create_subprocess_shell( + "git submodule foreach --quiet 'if [ -n \"$(git status --porcelain)\" ]; then echo $path; fi'", + stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE, + ) + stdout, stderr = await proc.communicate() + if stderr: + print(f"Error checking submodules:\n{stderr.decode()}") + return + + submodule_paths = stdout.decode().strip().split('\n') + git_user, git_pass, _ = get_git_credentials() + + for submodule_path in submodule_paths: + if not submodule_path: + continue + + abs_path = os.path.abspath(submodule_path) + name = submodule_info.get(abs_path, submodule_path) + print(f"Processing submodule: {name} at {abs_path}") + + commit_msg = gen_commit(abs_path) + + proc = await asyncio.create_subprocess_exec( + "git", "remote", "get-url", "origin", + cwd=abs_path, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE, + ) + stdout, stderr = await proc.communicate() + if stderr: + print(f"Error getting remote URL for {name}:\n{stderr.decode()}") + continue + + remote_url = stdout.decode().strip().replace(".git", "").replace("https://", "") + remote_url = f"https://{git_user}:{git_pass}@{remote_url}" + + commands = [ + f"cd {abs_path} && git add .", + f'cd {abs_path} && git commit -m "{commit_msg}" || true', + f"cd {abs_path} && git push {remote_url} || true", + ] + + for cmd in commands: + proc = await asyncio.create_subprocess_shell( + cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE + ) + stdout, stderr = await proc.communicate() + if stdout: + print(stdout.decode()) + if stderr: + print(f"Submodule '{name}' stderr:\n{stderr.decode()}") + + +async def push_code(): + git_user, git_pass, project_path = get_git_credentials() os.chdir(project_path) print(f"Changed to project path: {project_path}") - - # Generate commit message + + proc = await asyncio.create_subprocess_exec( + "git", "remote", "get-url", "origin", + stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE, + ) + stdout, stderr = await proc.communicate() + if stderr: + print(f"Failed to get remote URL: {stderr.decode()}") + git_repo = stdout.decode().strip().replace(".git", "").replace("https://", "") + remote_url = f"https://{git_user}:{git_pass}@{git_repo}" + + await commit_and_push_submodules() + commit_msg = gen_commit(project_path) print(f"Generated commit message:\n{commit_msg}\n") - # Pull, add, commit, and push - remote_url = f"https://{git_user}:{git_pass}@{git_repo}" + commands = [ + f"git pull {remote_url} || true", + "git add .", + f'git commit -m "{commit_msg}" || true', + f"git push {remote_url} || true", + ] - # Execute git commands in sequence - os.system(f"git pull {remote_url} || true") - os.system("git add .") - os.system(f'git commit -m "{commit_msg}" || true') - os.system(f"git push {remote_url} || true") + for cmd in commands: + proc = await asyncio.create_subprocess_shell( + cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE + ) + stdout, stderr = await proc.communicate() + if proc.returncode != 0: + print(f"Command failed: {cmd}") + print(stderr.decode()) + elif stderr: + # Not an error; just info output + print(stderr.decode()) if __name__ == "__main__": - push_code() + asyncio.run(push_code())