| """ |
| GitHub operations module for cloud coder agents. |
| Handles commits, branches, and PR creation using GitHub CLI. |
| """ |
|
|
| import subprocess |
| from pathlib import Path |
| from typing import Optional |
|
|
|
|
| REPO_DIR = Path("/workspace/OpenSIN-Code") |
| if not REPO_DIR.exists(): |
| REPO_DIR = Path("/home/user/project/OpenSIN-Code") |
| if not REPO_DIR.exists(): |
| REPO_DIR = Path.cwd().parent / "OpenSIN-Code" |
|
|
|
|
| class GitHubOps: |
| """Handles all GitHub-related operations.""" |
|
|
| def __init__(self, repo_dir: Optional[Path] = None): |
| self.repo_dir = repo_dir or REPO_DIR |
|
|
| def _run_git(self, args: list[str], capture: bool = True) -> subprocess.CompletedProcess: |
| """Run a git command in the repository.""" |
| cmd = ["git"] + args |
| return subprocess.run( |
| cmd, cwd=str(self.repo_dir), capture_output=capture, text=True, check=False |
| ) |
|
|
| def _run_gh(self, args: list[str], capture: bool = True) -> subprocess.CompletedProcess: |
| """Run a gh CLI command.""" |
| cmd = ["gh"] + args |
| return subprocess.run(cmd, capture_output=capture, text=True, check=False) |
|
|
| def ensure_branch(self, branch_name: str, base: str = "main") -> bool: |
| """Ensure branch exists, create from base if not.""" |
| |
| result = self._run_git(["branch", "--list", branch_name]) |
| if branch_name in result.stdout: |
| |
| checkout = self._run_git(["checkout", branch_name]) |
| return checkout.returncode == 0 |
|
|
| |
| checkout = self._run_git(["checkout", base]) |
| if checkout.returncode != 0: |
| return False |
|
|
| pull = self._run_git(["pull", "origin", base]) |
| if pull.returncode != 0: |
| return False |
|
|
| create = self._run_git(["checkout", "-b", branch_name]) |
| return create.returncode == 0 |
|
|
| def commit_changes(self, message: str) -> Optional[str]: |
| """Stage and commit all changes.""" |
| |
| add = self._run_git(["add", "."]) |
| if add.returncode != 0: |
| raise RuntimeError(f"git add failed: {add.stderr}") |
|
|
| |
| commit = self._run_git(["commit", "-m", message]) |
| if commit.returncode != 0: |
| if "nothing to commit" in commit.stderr.lower(): |
| return None |
| raise RuntimeError(f"git commit failed: {commit.stderr}") |
|
|
| |
| sha = commit.stdout.strip().split()[-1] if commit.stdout else "unknown" |
| return sha |
|
|
| def push_branch(self, branch_name: str, force: bool = False) -> bool: |
| """Push branch to origin.""" |
| args = ["push", "origin", branch_name] |
| if force: |
| args.append("--force") |
| push = self._run_git(args) |
| return push.returncode == 0 |
|
|
| def create_pr( |
| self, title: str, body: str, base: str = "main", head: str = None |
| ) -> Optional[str]: |
| """Create a pull request using gh CLI.""" |
| if not head: |
| |
| branch_result = self._run_git(["branch", "--show-current"]) |
| head = branch_result.stdout.strip() |
|
|
| args = ["pr", "create", "--title", title, "--body", body, "--base", base, "--head", head] |
|
|
| result = self._run_gh(args) |
| if result.returncode == 0: |
| |
| lines = result.stdout.strip().split("\n") |
| for line in lines: |
| if "https://github.com" in line: |
| return line.strip() |
| return result.stdout |
| else: |
| if "already exists" in result.stderr.lower(): |
| |
| list_result = self._run_gh( |
| ["pr", "list", "--head", head, "--json", "url", "--jq", ".[0].url"] |
| ) |
| if list_result.returncode == 0 and list_result.stdout.strip(): |
| return list_result.stdout.strip() |
| return None |
|
|
| def get_issue(self, issue_number: int) -> Optional[dict]: |
| """Fetch issue details using gh CLI.""" |
| result = self._run_gh( |
| ["issue", "view", str(issue_number), "--json", "number,title,body,labels,state"] |
| ) |
|
|
| if result.returncode == 0: |
| try: |
| return json.loads(result.stdout) |
| except json.JSONDecodeError: |
| return None |
| return None |
|
|
| def get_current_branch(self) -> str: |
| """Get current branch name.""" |
| result = self._run_git(["branch", "--show-current"]) |
| return result.stdout.strip() if result.returncode == 0 else "main" |
|
|
| def branch_exists_on_remote(self, branch_name: str) -> bool: |
| """Check if branch exists on remote.""" |
| result = self._run_git(["ls-remote", "--heads", "origin", branch_name]) |
| return result.returncode == 0 and bool(result.stdout.strip()) |
|
|
| def rebase_main(self) -> bool: |
| """Rebase current branch on main.""" |
| current = self.get_current_branch() |
| if current == "main": |
| return True |
|
|
| |
| fetch = self._run_git(["fetch", "origin", "main"]) |
| if fetch.returncode != 0: |
| return False |
|
|
| |
| rebase = self._run_git(["rebase", "origin/main"]) |
| return rebase.returncode == 0 |
|
|