NeuralAI / tools /git_assistant.py
Subject-Emu-5259's picture
Push NeuralAI project files - training data, scripts, services, knowledge base
38b4eff verified
Raw
History Blame Contribute Delete
15.6 kB
# tools/git_assistant.py
#
# Git operations assistant
# - Status, log, diff
# - Stage, commit, push
# - Branch management
# - PR creation (via gh CLI)
import subprocess
import os
from typing import Dict, Any, List, Optional
from pathlib import Path
class GitAssistant:
"""Git operations from NeuralAI chat."""
def __init__(self, repo_path: str = "/home/workspace"):
self.repo_path = Path(repo_path)
def _run_git(self, args: List[str], timeout: int = 30) -> Dict[str, Any]:
"""Execute a git command."""
try:
result = subprocess.run(
['git'] + args,
capture_output=True,
text=True,
timeout=timeout,
cwd=self.repo_path,
env={**os.environ, 'GIT_TERMINAL_PROMPT': '0'}
)
return {
"success": result.returncode == 0,
"output": result.stdout.strip(),
"error": result.stderr.strip() if result.returncode != 0 else "",
"exit_code": result.returncode
}
except subprocess.TimeoutExpired:
return {
"success": False,
"error": f"Git command timed out after {timeout}s",
"output": "",
"exit_code": -1
}
except FileNotFoundError:
return {
"success": False,
"error": "Git is not installed",
"output": "",
"exit_code": -1
}
except Exception as e:
return {
"success": False,
"error": str(e),
"output": "",
"exit_code": -1
}
def _run_gh(self, args: List[str], timeout: int = 30) -> Dict[str, Any]:
"""Execute a GitHub CLI command."""
try:
result = subprocess.run(
['gh'] + args,
capture_output=True,
text=True,
timeout=timeout,
cwd=self.repo_path
)
return {
"success": result.returncode == 0,
"output": result.stdout.strip(),
"error": result.stderr.strip() if result.returncode != 0 else "",
"exit_code": result.returncode
}
except FileNotFoundError:
return {
"success": False,
"error": "GitHub CLI (gh) is not installed. Install from: https://cli.github.com/",
"output": "",
"exit_code": -1
}
except Exception as e:
return {
"success": False,
"error": str(e),
"output": "",
"exit_code": -1
}
def is_repo(self) -> Dict[str, Any]:
"""Check if current directory is a git repository."""
result = self._run_git(['rev-parse', '--is-inside-work-tree'])
return {
"success": True,
"is_repo": result["success"] and result["output"] == "true",
"path": str(self.repo_path)
}
def status(self) -> Dict[str, Any]:
"""
Get repository status.
Returns:
{
"success": bool,
"branch": str,
"staged": list,
"modified": list,
"untracked": list,
"ahead": int,
"behind": int
}
"""
# Check if repo
if not self.is_repo()["is_repo"]:
return {
"success": False,
"error": "Not a git repository",
"branch": "",
"staged": [],
"modified": [],
"untracked": [],
"ahead": 0,
"behind": 0
}
# Get branch
branch_result = self._run_git(['branch', '--show-current'])
branch = branch_result["output"] or "HEAD"
# Get status
status_result = self._run_git(['status', '--porcelain'])
staged = []
modified = []
untracked = []
for line in status_result["output"].split('\n'):
if not line:
continue
status_code = line[:2]
filename = line[3:]
if status_code[0] != ' ' and status_code[0] != '?':
staged.append(filename)
if status_code[1] == 'M':
modified.append(filename)
if status_code[0] == '?':
untracked.append(filename)
# Get ahead/behind
ahead = 0
behind = 0
if branch != "HEAD":
count_result = self._run_git(['rev-list', '--left-right', '--count', f'origin/{branch}...HEAD'])
if count_result["success"] and count_result["output"]:
parts = count_result["output"].split()
if len(parts) == 2:
behind = int(parts[0])
ahead = int(parts[1])
return {
"success": True,
"branch": branch,
"staged": staged,
"modified": modified,
"untracked": untracked,
"ahead": ahead,
"behind": behind
}
def log(self, count: int = 10, oneline: bool = True) -> Dict[str, Any]:
"""
Get commit history.
Returns:
{
"success": bool,
"commits": [{"hash": str, "author": str, "date": str, "message": str}]
}
"""
if not self.is_repo()["is_repo"]:
return {
"success": False,
"error": "Not a git repository",
"commits": []
}
if oneline:
result = self._run_git(['log', f'-{count}', '--oneline', '--format=%h %an %ar %s'])
commits = []
for line in result["output"].split('\n'):
if not line:
continue
parts = line.split(' ', 3)
if len(parts) >= 4:
commits.append({
"hash": parts[0],
"author": parts[1],
"date": parts[2],
"message": parts[3]
})
else:
result = self._run_git(['log', f'-{count}', '--format=%H%n%an%n%ar%n%s%n---'])
commits = []
blocks = result["output"].split('---\n')
for block in blocks:
if not block.strip():
continue
lines = block.strip().split('\n')
if len(lines) >= 4:
commits.append({
"hash": lines[0],
"author": lines[1],
"date": lines[2],
"message": lines[3]
})
return {
"success": True,
"commits": commits,
"count": len(commits)
}
def diff(self, staged: bool = False, file: Optional[str] = None) -> Dict[str, Any]:
"""Get diff of changes."""
args = ['diff']
if staged:
args.append('--staged')
if file:
args.extend(['--', file])
result = self._run_git(args)
return {
"success": True,
"diff": result["output"],
"has_changes": bool(result["output"])
}
def add(self, files: List[str] = None) -> Dict[str, Any]:
"""
Stage files for commit.
Args:
files: List of files to stage (or ['.'] for all)
"""
if not self.is_repo()["is_repo"]:
return {"success": False, "error": "Not a git repository"}
if files is None:
files = ['.']
result = self._run_git(['add'] + files)
return {
"success": result["success"],
"files": files,
"error": result.get("error", "")
}
def commit(self, message: str) -> Dict[str, Any]:
"""Create a commit with the given message."""
if not self.is_repo()["is_repo"]:
return {"success": False, "error": "Not a git repository"}
result = self._run_git(['commit', '-m', message])
if result["success"]:
# Get the new commit hash
hash_result = self._run_git(['rev-parse', 'HEAD'])
return {
"success": True,
"message": message,
"commit": hash_result["output"][:7]
}
return {
"success": False,
"error": result.get("error", "Commit failed")
}
def push(self, branch: Optional[str] = None, force: bool = False) -> Dict[str, Any]:
"""Push to remote."""
if not self.is_repo()["is_repo"]:
return {"success": False, "error": "Not a git repository"}
args = ['push']
if force:
args.append('--force')
if branch:
args.extend(['origin', branch])
result = self._run_git(args, timeout=60)
return {
"success": result["success"],
"output": result.get("output", ""),
"error": result.get("error", "")
}
def pull(self, branch: Optional[str] = None) -> Dict[str, Any]:
"""Pull from remote."""
if not self.is_repo()["is_repo"]:
return {"success": False, "error": "Not a git repository"}
args = ['pull']
if branch:
args.extend(['origin', branch])
result = self._run_git(args, timeout=60)
return {
"success": result["success"],
"output": result.get("output", ""),
"error": result.get("error", "")
}
def branch(self, name: Optional[str] = None, create: bool = False,
delete: bool = False, list_all: bool = False) -> Dict[str, Any]:
"""
Manage branches.
Args:
name: Branch name
create: Create new branch
delete: Delete branch
list_all: List all branches
"""
if not self.is_repo()["is_repo"]:
return {"success": False, "error": "Not a git repository"}
if list_all or (not name and not create and not delete):
result = self._run_git(['branch', '-a'])
branches = [b.strip().lstrip('* ') for b in result["output"].split('\n') if b.strip()]
return {
"success": True,
"branches": branches,
"current": self._run_git(['branch', '--show-current'])["output"]
}
if create and name:
result = self._run_git(['checkout', '-b', name])
return {
"success": result["success"],
"branch": name,
"error": result.get("error", "")
}
if delete and name:
result = self._run_git(['branch', '-D', name])
return {
"success": result["success"],
"deleted": name,
"error": result.get("error", "")
}
if name:
result = self._run_git(['checkout', name])
return {
"success": result["success"],
"branch": name,
"error": result.get("error", "")
}
return {"success": False, "error": "Invalid branch operation"}
def remote(self) -> Dict[str, Any]:
"""Get remote information."""
result = self._run_git(['remote', '-v'])
remotes = {}
for line in result["output"].split('\n'):
if not line:
continue
parts = line.split()
if len(parts) >= 2:
name = parts[0]
url = parts[1]
if name not in remotes:
remotes[name] = url
return {
"success": True,
"remotes": remotes
}
def stash(self, message: Optional[str] = None) -> Dict[str, Any]:
"""Stash changes."""
args = ['stash']
if message:
args.extend(['-m', message])
result = self._run_git(args)
return {
"success": result["success"],
"output": result.get("output", ""),
"error": result.get("error", "")
}
def stash_pop(self) -> Dict[str, Any]:
"""Apply and remove the latest stash."""
result = self._run_git(['stash', 'pop'])
return {
"success": result["success"],
"output": result.get("output", ""),
"error": result.get("error", "")
}
# GitHub CLI integration
def pr_create(self, title: str, body: str = "",
base: str = "main", draft: bool = False) -> Dict[str, Any]:
"""
Create a pull request using GitHub CLI.
Requires: gh CLI installed and authenticated
"""
args = ['pr', 'create', '--title', title, '--base', base]
if body:
args.extend(['--body', body])
if draft:
args.append('--draft')
result = self._run_gh(args)
if result["success"]:
return {
"success": True,
"url": result["output"],
"title": title
}
return {
"success": False,
"error": result.get("error", "Failed to create PR")
}
def pr_list(self, state: str = "open") -> Dict[str, Any]:
"""List pull requests."""
result = self._run_gh(['pr', 'list', '--state', state, '--json', 'number,title,url'])
if result["success"]:
import json
try:
prs = json.loads(result["output"])
return {
"success": True,
"prs": prs
}
except:
pass
return {
"success": False,
"error": result.get("error", "Failed to list PRs"),
"prs": []
}
def issue_create(self, title: str, body: str = "") -> Dict[str, Any]:
"""Create an issue using GitHub CLI."""
args = ['issue', 'create', '--title', title]
if body:
args.extend(['--body', body])
result = self._run_gh(args)
if result["success"]:
return {
"success": True,
"url": result["output"],
"title": title
}
return {
"success": False,
"error": result.get("error", "Failed to create issue")
}
if __name__ == "__main__":
# Test the git assistant
git = GitAssistant()
print("Checking if repo...")
print(git.is_repo())
print("\nGit status:")
print(git.status())
print("\nGit log:")
print(git.log(count=5))
print("\nGit branches:")
print(git.branch(list_all=True))