#!/usr/bin/env python3 import sys import json import base64 import urllib.request import urllib.parse import argparse GITEA_API_URL = "https://git.saqut.com/api/v1" REPO_PATH = "repos/saqut/saqut-compiler" def get_credentials(): try: import os cred_path = os.path.expanduser("~/.git-credentials") if os.path.exists(cred_path): with open(cred_path, "r") as f: for line in f: if "git.saqut.com" in line: # Format: https://username:password@git.saqut.com url_part = line.strip().split("@")[0] auth_part = url_part.split("://")[1] username, password = auth_part.split(":") # URL decode password password = urllib.parse.unquote(password) return username, password except Exception as e: sys.stderr.write(f"Credentials load warning: {e}\n") return "saqut", "" def make_request(endpoint, method="GET", data=None): username, password = get_credentials() url = f"{GITEA_API_URL}/{endpoint}" headers = { "Content-Type": "application/json", "Accept": "application/json", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" } auth_str = f"{username}:{password}" auth_b64 = base64.b64encode(auth_str.encode('utf-8')).decode('utf-8') headers["Authorization"] = f"Basic {auth_b64}" req_data = None if data is not None: req_data = json.dumps(data).encode('utf-8') req = urllib.request.Request(url, data=req_data, headers=headers, method=method) try: with urllib.request.urlopen(req) as response: res_data = response.read() if response.status in (200, 201): return json.loads(res_data.decode('utf-8')) elif response.status == 204: return {"success": True} return res_data.decode('utf-8') except urllib.error.HTTPError as e: sys.stderr.write(f"HTTP Error: {e.code} - {e.reason}\n") try: err_body = e.read().decode('utf-8') sys.stderr.write(f"Details: {err_body}\n") except Exception: pass sys.exit(1) except Exception as e: sys.stderr.write(f"Connection Error: {e}\n") sys.exit(1) def list_issues(state="open"): endpoint = f"{REPO_PATH}/issues?state={state}&limit=100" issues = make_request(endpoint) print(f"\n--- Gitea {state.upper()} Issues ---") for issue in issues: print(f"#{issue['number']} - {issue['title']} ({issue['state']})") if issue.get('assignee'): print(f" Assignee: {issue['assignee']['login']}") def get_issue(number): endpoint = f"{REPO_PATH}/issues/{number}" issue = make_request(endpoint) print(json.dumps(issue, indent=2, ensure_ascii=False)) def create_issue(title, body, assignee=None): endpoint = f"{REPO_PATH}/issues" data = { "title": title, "body": body } if assignee: data["assignees"] = [assignee] res = make_request(endpoint, method="POST", data=data) print(f"Created issue #{res['number']}: {res['title']}") def edit_issue(number, title=None, body=None, state=None, assignee=None): endpoint = f"{REPO_PATH}/issues/{number}" data = {} if title is not None: data["title"] = title if body is not None: data["body"] = body if state is not None: data["state"] = state if assignee is not None: data["assignees"] = [assignee] if assignee else [] res = make_request(endpoint, method="PATCH", data=data) print(f"Updated issue #{res['number']} to state: {res['state']}") def comment_issue(number, body): endpoint = f"{REPO_PATH}/issues/{number}/comments" data = { "body": body } res = make_request(endpoint, method="POST", data=data) print(f"Added comment to issue #{number} (Comment ID: {res['id']})") def main(): parser = argparse.ArgumentParser(description="Gitea Issue Manager for saQut") subparsers = parser.add_subparsers(dest="command", help="Subcommands") # List list_parser = subparsers.add_parser("list", help="List issues") list_parser.add_argument("--state", choices=["open", "closed", "all"], default="open", help="State of issues") # Get get_parser = subparsers.add_parser("get", help="Get issue detail") get_parser.add_argument("number", type=int, help="Issue number") # Create create_parser = subparsers.add_parser("create", help="Create a new issue") create_parser.add_argument("--title", required=True, help="Issue title") create_parser.add_argument("--body", default="", help="Issue body") create_parser.add_argument("--assignee", help="Assignee username") # Edit edit_parser = subparsers.add_parser("edit", help="Edit an existing issue") edit_parser.add_argument("number", type=int, help="Issue number") edit_parser.add_argument("--title", help="New title") edit_parser.add_argument("--body", help="New body") edit_parser.add_argument("--state", choices=["open", "closed"], help="New state") edit_parser.add_argument("--assignee", help="New assignee (empty string to unassign)") # Comment comment_parser = subparsers.add_parser("comment", help="Add a comment to an issue") comment_parser.add_argument("number", type=int, help="Issue number") comment_parser.add_argument("--body", required=True, help="Comment body") args = parser.parse_args() if not args.command: parser.print_help() sys.exit(0) if args.command == "list": list_issues(args.state) elif args.command == "get": get_issue(args.number) elif args.command == "create": create_issue(args.title, args.body, args.assignee) elif args.command == "edit": edit_issue(args.number, args.title, args.body, args.state, args.assignee) elif args.command == "comment": comment_issue(args.number, args.body) if __name__ == "__main__": main()