import argparse import subprocess import sys # push origin HEAD:refs/for/main # HEAD: The target branch # origin: The target repository (not a fork!) # HEAD: The local branch containing the changes you are proposing TARGET_REMOTE_REPOSITORY = "origin" DEFAULT_TARGET_BRANCH = "main" def run_git_command(command: list) -> tuple[int, str, str]: """Run a git command and return exit code, stdout, and stderr.""" try: result = subprocess.run(command, capture_output=True, text=True, check=False) return result.returncode, result.stdout.strip(), result.stderr.strip() except Exception as e: return 1, "", str(e) def get_latest_commit_info() -> tuple[str, str]: """Get the title and body of the latest commit.""" exit_code, commit_msg, error = run_git_command( ["git", "log", "-1", "--pretty=format:%B"] ) if exit_code != 0: print(f"Error getting commit info: {error}") sys.exit(1) lines = commit_msg.strip().split("\n") title = lines[0].strip() if lines else "" body_lines = [] for line in lines[1:]: if body_lines or line.strip(): body_lines.append(line) body = "\n".join(body_lines).strip() return title, body def create_agit_push( remote: str = "origin", branch: str = "main", topic: str | None = None, title: str | None = None, description: str | None = None, force_push: bool = False, local_branch: str = "HEAD", ) -> None: if topic is None or title is None: commit_title, _ = get_latest_commit_info() if topic is None: topic = commit_title if title is None: title = commit_title refspec = f"{local_branch}:refs/for/{branch}" push_cmd = ["git", "push", remote, refspec] push_cmd.extend(["-o", f"topic={topic}"]) if title: push_cmd.extend(["-o", f"title={title}"]) if description: escaped_desc = description.replace('"', '\\"') push_cmd.extend(["-o", f"description={escaped_desc}"]) if force_push: push_cmd.extend(["-o", "force-push"]) if description: print( f" Description: {description[:50]}..." if len(description) > 50 else f" Description: {description}" ) print() exit_code, stdout, stderr = run_git_command(push_cmd) if stdout: print(stdout) if stderr: print(stderr, file=sys.stderr) if exit_code != 0: print("\nPush failed!") sys.exit(exit_code) else: print("\nPush successful!") def cmd_create(args: argparse.Namespace) -> None: """Handle the create subcommand.""" create_agit_push( remote=args.remote, branch=args.branch, topic=args.topic, title=args.title, description=args.description, force_push=args.force, local_branch=args.local_branch, ) def create_parser() -> argparse.ArgumentParser: parser = argparse.ArgumentParser( prog="agit", description="AGit utility for creating and pulling PRs", formatter_class=argparse.RawDescriptionHelpFormatter, epilog=f""" The defaults that are assumed are: TARGET_REMOTE_REPOSITORY = {TARGET_REMOTE_REPOSITORY} DEFAULT_TARGET_BRANCH = {DEFAULT_TARGET_BRANCH} Examples: $ agit create Will create an AGit Pr with the latest commit message title as it's topic. $ agit create --topic "my-feature" Set a custom topic. $ agit create --force Force push to a certain topic """, ) subparsers = parser.add_subparsers(dest="subcommand", help="Commands") create_parser = subparsers.add_parser( "create", aliases=["c"], help="Create an AGit PR", formatter_class=argparse.RawDescriptionHelpFormatter, epilog=""" Examples: $ agit create Will create an AGit Pr with the latest commit message title as it's topic. $ agit create --topic "my-feature" Set a custom topic. $ agit create --force Force push to a certain topic """, ) create_parser.add_argument( "-r", "--remote", default=TARGET_REMOTE_REPOSITORY, help=f"Git remote to push to (default: {TARGET_REMOTE_REPOSITORY})", ) create_parser.add_argument( "-b", "--branch", default=DEFAULT_TARGET_BRANCH, help=f"Target branch for the PR (default: {DEFAULT_TARGET_BRANCH})", ) create_parser.add_argument( "-l", "--local-branch", default="HEAD", help="Local branch to push (default: HEAD)", ) create_parser.add_argument( "-t", "--topic", help="Set PR topic (default: last commit title)" ) create_parser.add_argument( "--title", help="Set the PR title (default: last commit title)" ) create_parser.add_argument( "--description", help="Override the PR description (default: commit body)" ) create_parser.add_argument( "-f", "--force", action="store_true", help="Force push the changes" ) create_parser.set_defaults(func=cmd_create) return parser def main() -> None: parser = create_parser() args = parser.parse_args() if args.subcommand is None: parser.print_help() sys.exit(0) args.func(args) if __name__ == "__main__": main()