#!/usr/bin/env python3 """ Minimal Gitea API client. Reads GITEA_* from environment or .env in repo root. """ from __future__ import annotations import argparse import json import sys from pathlib import Path from gitea_lib import ( issues_comment, issues_create, issues_get, issues_list_all, issues_list_page, issues_patch, load_dotenv, repo_file_content, repo_root, require_config, ) def cmd_issues_list(args: argparse.Namespace, base: str, token: str, owner: str, repo: str) -> None: if args.all_pages: items = issues_list_all( base, token, owner, repo, state=args.state, limit=args.limit ) else: _, items = issues_list_page( base, token, owner, repo, state=args.state, page=args.page, limit=args.limit, ) for it in items: num = it.get("number") title = it.get("title") st = it.get("state") print(f"#{num} [{st}] {title}") def cmd_issues_get(args: argparse.Namespace, base: str, token: str, owner: str, repo: str) -> None: status, payload = issues_get(base, token, owner, repo, args.number) print(json.dumps(payload, indent=2, ensure_ascii=False)) if status >= 400: sys.exit(1) def cmd_issues_create(args: argparse.Namespace, base: str, token: str, owner: str, repo: str) -> None: body = args.body or "" if args.body_file: body = Path(args.body_file).read_text(encoding="utf-8") status, payload = issues_create( base, token, owner, repo, title=args.title, body=body, labels=args.labels or [], ) print(json.dumps(payload, indent=2, ensure_ascii=False)) if status >= 400: sys.exit(1) def cmd_issues_comment(args: argparse.Namespace, base: str, token: str, owner: str, repo: str) -> None: body = args.body or "" if getattr(args, "body_file", None): body = Path(args.body_file).read_text(encoding="utf-8") if not body.strip(): sys.stderr.write("issues comment: --body oder --body-file mit Inhalt erforderlich\n") sys.exit(2) status, payload = issues_comment( base, token, owner, repo, args.number, body ) print(json.dumps(payload, indent=2, ensure_ascii=False)) if status >= 400: sys.exit(1) def cmd_issues_close(args: argparse.Namespace, base: str, token: str, owner: str, repo: str) -> None: status, payload = issues_patch( base, token, owner, repo, args.number, {"state": "closed"} ) print(json.dumps(payload, indent=2, ensure_ascii=False)) if status >= 400: sys.exit(1) def cmd_issues_reopen(args: argparse.Namespace, base: str, token: str, owner: str, repo: str) -> None: status, payload = issues_patch( base, token, owner, repo, args.number, {"state": "open"} ) print(json.dumps(payload, indent=2, ensure_ascii=False)) if status >= 400: sys.exit(1) def cmd_issues_edit(args: argparse.Namespace, base: str, token: str, owner: str, repo: str) -> None: fields: dict = {} if args.title is not None: fields["title"] = args.title.strip() if not fields["title"]: sys.stderr.write("issues edit: --title darf nicht leer sein\n") sys.exit(2) body: str | None = None if args.body_file: body = Path(args.body_file).read_text(encoding="utf-8") elif args.body is not None: body = args.body if body is not None: fields["body"] = body if not fields: sys.stderr.write( "issues edit: mindestens eines von --title, --body oder --body-file setzen\n" ) sys.exit(2) status, payload = issues_patch(base, token, owner, repo, args.number, fields) print(json.dumps(payload, indent=2, ensure_ascii=False)) if status >= 400: sys.exit(1) def cmd_repo_contents(args: argparse.Namespace, base: str, token: str, owner: str, repo: str) -> None: status, payload = repo_file_content( base, token, owner, repo, args.path, ref=args.ref or "" ) if status >= 400: print(json.dumps(payload, indent=2, ensure_ascii=False)) sys.exit(1) if isinstance(payload, dict) and payload.get("encoding") == "text": print(payload.get("content", "")) else: print(json.dumps(payload, indent=2, ensure_ascii=False)) def main() -> None: if hasattr(sys.stdout, "reconfigure"): try: sys.stdout.reconfigure(encoding="utf-8") except Exception: pass root = repo_root() load_dotenv(root) parser = argparse.ArgumentParser(description="Gitea API helper") sub = parser.add_subparsers(dest="domain", required=True) p_issues = sub.add_parser("issues", help="Issues") i_sub = p_issues.add_subparsers(dest="issues_cmd", required=True) p_il = i_sub.add_parser("list", help="List issues") p_il.add_argument("--state", default="open", choices=["open", "closed", "all"]) p_il.add_argument("--limit", type=int, default=50) p_il.add_argument("--page", type=int, default=1) p_il.add_argument( "--all-pages", action="store_true", help="Alle Seiten abfragen (Vorsicht bei sehr vielen Issues)", ) p_il.set_defaults(_handler=cmd_issues_list) p_ig = i_sub.add_parser("get", help="Get one issue") p_ig.add_argument("number", type=int) p_ig.set_defaults(_handler=cmd_issues_get) p_ic = i_sub.add_parser("create", help="Create issue") p_ic.add_argument("--title", required=True) p_ic.add_argument("--body", default="") p_ic.add_argument("--body-file") p_ic.add_argument("--labels", nargs="*", default=[]) p_ic.set_defaults(_handler=cmd_issues_create) p_co = i_sub.add_parser("comment", help="Add comment") p_co.add_argument("number", type=int) p_co.add_argument("--body", default="") p_co.add_argument("--body-file", help="Kommentar aus Datei (UTF-8); überschreibt --body wenn gesetzt") p_co.set_defaults(_handler=cmd_issues_comment) p_cl = i_sub.add_parser("close", help="Close issue") p_cl.add_argument("number", type=int) p_cl.set_defaults(_handler=cmd_issues_close) p_ro = i_sub.add_parser("reopen", help="Reopen issue") p_ro.add_argument("number", type=int) p_ro.set_defaults(_handler=cmd_issues_reopen) p_ed = i_sub.add_parser( "edit", help="Issue per PATCH ändern (Titel und/oder Beschreibung; für große Texte --body-file)", ) p_ed.add_argument("number", type=int) p_ed.add_argument("--title", default=None, help="Neuer Titel") p_ed.add_argument("--body", default=None, help="Neue Beschreibung (Markdown)") p_ed.add_argument( "--body-file", default=None, help="Beschreibung aus Datei (UTF-8); überschreibt --body wenn beides gesetzt", ) p_ed.set_defaults(_handler=cmd_issues_edit) p_repo = sub.add_parser("repo", help="Repository (API)") r_sub = p_repo.add_subparsers(dest="repo_cmd", required=True) p_rc = r_sub.add_parser("file", help="Get file or directory metadata/content") p_rc.add_argument("path") p_rc.add_argument("--ref", default="", help="branch/tag/commit") p_rc.set_defaults(_handler=cmd_repo_contents) args = parser.parse_args() try: base, token, owner, reponame = require_config() except RuntimeError as e: sys.stderr.write(str(e) + "\n") sys.exit(1) handler = args._handler handler(args, base, token, owner, reponame) if __name__ == "__main__": main()