170 lines
6.1 KiB
Python
Executable File
170 lines
6.1 KiB
Python
Executable File
#!/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", "<Cloud2022>"
|
|
|
|
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()
|