Files
qianyi-skills/gitea-manager/gitea_client.py
2026-03-13 05:12:51 +00:00

424 lines
18 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""Gitea API Client - 封装 Gitea REST API 的所有操作"""
import os
import json
import requests
from typing import Optional, Dict, List, Any
class GiteaClient:
"""Gitea API 客户端"""
def __init__(self, url: Optional[str] = None, token: Optional[str] = None):
self.url = url or os.environ.get("GITEA_URL", "http://localhost:3000")
self.token = token or os.environ.get("GITEA_TOKEN", "")
self.session = requests.Session()
self.session.headers.update({
"Authorization": f"token {self.token}",
"Content-Type": "application/json",
"Accept": "application/json"
})
if not self.token:
raise ValueError("GITEA_TOKEN 环境变量未设置")
def _request(self, method: str, path: str, **kwargs) -> requests.Response:
"""发送 API 请求"""
url = f"{self.url.rstrip('/')}{path}"
response = self.session.request(method, url, **kwargs)
response.raise_for_status()
return response
# ==================== 用户操作 ====================
def get_current_user(self) -> Dict:
"""获取当前认证用户信息"""
return self._request("GET", "/user").json()
def get_user(self, username: str) -> Dict:
"""获取指定用户信息"""
return self._request("GET", f"/users/{username}").json()
def list_user_repos(self, username: str, page: int = 1, limit: int = 50) -> List[Dict]:
"""列出用户的所有仓库"""
return self._request("GET", f"/users/{username}/repos", params={"page": page, "limit": limit}).json()
def create_user(self, email: str, username: str, password: str, **kwargs) -> Dict:
"""创建用户(需要管理员权限)"""
data = {"email": email, "username": username, "password": password}
data.update(kwargs)
return self._request("POST", "/admin/users", json=data).json()
def delete_user(self, username: str) -> None:
"""删除用户(需要管理员权限)"""
self._request("DELETE", f"/admin/users/{username}")
# ==================== 组织操作 ====================
def list_orgs(self, page: int = 1, limit: int = 50) -> List[Dict]:
"""列出当前用户所属组织"""
return self._request("GET", "/user/orgs", params={"page": page, "limit": limit}).json()
def get_org(self, username: str) -> Dict:
"""获取组织信息"""
return self._request("GET", f"/orgs/{username}").json()
def create_org(self, username: str, email: str, **kwargs) -> Dict:
"""创建组织"""
data = {"username": username, "email": email}
data.update(kwargs)
return self._request("POST", "/admin/orgs", json=data).json()
def list_org_repos(self, org: str, page: int = 1, limit: int = 50) -> List[Dict]:
"""列出组织的所有仓库"""
return self._request("GET", f"/orgs/{org}/repos", params={"page": page, "limit": limit}).json()
# ==================== 仓库操作 ====================
def list_my_repos(self, page: int = 1, limit: int = 50) -> List[Dict]:
"""列出当前用户的仓库"""
return self._request("GET", "/user/repos", params={"page": page, "limit": limit}).json()
def get_repo(self, owner: str, repo: str) -> Dict:
"""获取仓库信息"""
return self._request("GET", f"/repos/{owner}/{repo}").json()
def create_repo(self, name: str, description: str = "", private: bool = False,
auto_init: bool = True, **kwargs) -> Dict:
"""创建仓库"""
data = {
"name": name,
"description": description,
"private": private,
"auto_init": auto_init
}
data.update(kwargs)
return self._request("POST", "/user/repos", json=data).json()
def create_org_repo(self, org: str, name: str, description: str = "",
private: bool = False, auto_init: bool = True, **kwargs) -> Dict:
"""在组织下创建仓库"""
data = {
"name": name,
"description": description,
"private": private,
"auto_init": auto_init
}
data.update(kwargs)
return self._request("POST", f"/orgs/{org}/repos", json=data).json()
def delete_repo(self, owner: str, repo: str) -> None:
"""删除仓库"""
self._request("DELETE", f"/repos/{owner}/{repo}")
def get_repo_info(self, owner: str, repo: str) -> Dict:
"""获取仓库详细信息"""
return self._request("GET", f"/repos/{owner}/{repo}").json()
def update_repo(self, owner: str, repo: str, **kwargs) -> Dict:
"""更新仓库设置"""
return self._request("PATCH", f"/repos/{owner}/{repo}", json=kwargs).json()
# ==================== 分支操作 ====================
def list_branches(self, owner: str, repo: str, page: int = 1, limit: int = 50) -> List[Dict]:
"""列出仓库分支"""
return self._request("GET", f"/repos/{owner}/{repo}/branches",
params={"page": page, "limit": limit}).json()
def get_branch(self, owner: str, repo: str, branch: str) -> Dict:
"""获取分支信息"""
return self._request("GET", f"/repos/{owner}/{repo}/branches/{branch}").json()
def create_branch(self, owner: str, repo: str, branch_name: str, from_branch: str) -> Dict:
"""创建分支"""
data = {
"new_branch_name": branch_name,
"from_branch": from_branch
}
return self._request("POST", f"/repos/{owner}/{repo}/branches", json=data).json()
def delete_branch(self, owner: str, repo: str, branch: str) -> None:
"""删除分支"""
self._request("DELETE", f"/repos/{owner}/{repo}/branches/{branch}")
def list_protected_branches(self, owner: str, repo: str) -> List[Dict]:
"""列出受保护分支"""
return self._request("GET", f"/repos/{owner}/{repo}/protected_branches").json()
def get_protected_branch(self, owner: str, repo: str, branch: str) -> Dict:
"""获取受保护分支规则"""
return self._request("GET", f"/repos/{owner}/{repo}/protected_branches/{branch}").json()
def set_protected_branch(self, owner: str, repo: str, branch: str, **kwargs) -> Dict:
"""设置分支保护规则"""
return self._request("PUT", f"/repos/{owner}/{repo}/protected_branches/{branch}", json=kwargs).json()
# ==================== 文件操作 ====================
def get_file_content(self, owner: str, repo: str, path: str, ref: str = "main") -> str:
"""获取文件内容Base64 编码)"""
response = self._request("GET", f"/repos/{owner}/{repo}/raw/{path}", params={"ref": ref})
import base64
return base64.b64encode(response.content).decode('utf-8')
def get_file_info(self, owner: str, repo: str, path: str, ref: str = "main") -> Dict:
"""获取文件信息"""
return self._request("GET", f"/repos/{owner}/{repo}/contents/{path}", params={"ref": ref}).json()
def create_or_update_file(self, owner: str, repo: str, path: str, content: str,
message: str, branch: str, email: str, name: str) -> Dict:
"""创建或更新文件"""
import base64
data = {
"content": base64.b64encode(content.encode()).decode('utf-8'),
"message": message,
"branch": branch,
"author": {"email": email, "name": name},
"committer": {"email": email, "name": name}
}
return self._request("POST", f"/repos/{owner}/{repo}/contents/{path}", json=data).json()
def delete_file(self, owner: str, repo: str, path: str, message: str,
branch: str, sha: str, email: str, name: str) -> Dict:
"""删除文件"""
data = {
"message": message,
"branch": branch,
"sha": sha,
"author": {"email": email, "name": name},
"committer": {"email": email, "name": name}
}
return self._request("DELETE", f"/repos/{owner}/{repo}/contents/{path}", json=data).json()
def list_directory(self, owner: str, repo: str, path: str = "",
ref: str = "main", page: int = 1, limit: int = 100) -> List[Dict]:
"""列出目录内容"""
return self._request("GET", f"/repos/{owner}/{repo}/contents/{path}",
params={"ref": ref, "page": page, "limit": limit}).json()
# ==================== Issue 操作 ====================
def list_issues(self, owner: str, repo: str, state: str = "open",
page: int = 1, limit: int = 50, **kwargs) -> List[Dict]:
"""列出仓库的 Issue"""
params = {"state": state, "page": page, "limit": limit}
params.update(kwargs)
return self._request("GET", f"/repos/{owner}/{repo}/issues", params=params).json()
def get_issue(self, owner: str, repo: str, index: int) -> Dict:
"""获取 Issue 详情"""
return self._request("GET", f"/repos/{owner}/{repo}/issues/{index}").json()
def create_issue(self, owner: str, repo: str, title: str, body: str = "",
**kwargs) -> Dict:
"""创建 Issue"""
data = {"title": title, "body": body}
data.update(kwargs)
return self._request("POST", f"/repos/{owner}/{repo}/issues", json=data).json()
def update_issue(self, owner: str, repo: str, index: int, **kwargs) -> Dict:
"""更新 Issue"""
return self._request("PATCH", f"/repos/{owner}/{repo}/issues/{index}", json=kwargs).json()
def close_issue(self, owner: str, repo: str, index: int) -> Dict:
"""关闭 Issue"""
return self.update_issue(owner, repo, index, state="closed")
def list_issue_comments(self, owner: str, repo: str, issue_index: int,
page: int = 1, limit: int = 50) -> List[Dict]:
"""列出 Issue 的评论"""
return self._request("GET", f"/repos/{owner}/{repo}/issues/{issue_index}/comments",
params={"page": page, "limit": limit}).json()
def create_issue_comment(self, owner: str, repo: str, issue_index: int, body: str) -> Dict:
"""创建 Issue 评论"""
return self._request("POST", f"/repos/{owner}/{repo}/issues/{issue_index}/comments",
json={"body": body}).json()
# ==================== Pull Request 操作 ====================
def list_pulls(self, owner: str, repo: str, state: str = "open",
page: int = 1, limit: int = 50) -> List[Dict]:
"""列出 Pull Request"""
return self._request("GET", f"/repos/{owner}/{repo}/pulls",
params={"state": state, "page": page, "limit": limit}).json()
def get_pull(self, owner: str, repo: str, index: int) -> Dict:
"""获取 Pull Request 详情"""
return self._request("GET", f"/repos/{owner}/{repo}/pulls/{index}").json()
def create_pull(self, owner: str, repo: str, title: str, body: str,
head: str, base: str, **kwargs) -> Dict:
"""创建 Pull Request"""
data = {
"title": title,
"body": body,
"head": head,
"base": base
}
data.update(kwargs)
return self._request("POST", f"/repos/{owner}/{repo}/pulls", json=data).json()
def update_pull(self, owner: str, repo: str, index: int, **kwargs) -> Dict:
"""更新 Pull Request"""
return self._request("PATCH", f"/repos/{owner}/{repo}/pulls/{index}", json=kwargs).json()
def merge_pull(self, owner: str, repo: str, index: int,
merge_message: str = "", merge_style: str = "merge") -> Dict:
"""合并 Pull Request"""
data = {
"merge_commit_message": merge_message,
"merge_style": merge_style
}
return self._request("POST", f"/repos/{owner}/{repo}/pulls/{index}/merge", json=data).json()
def list_pull_comments(self, owner: str, repo: str, index: int) -> List[Dict]:
"""列出 Pull Request 的评论"""
return self._request("GET", f"/repos/{owner}/{repo}/issues/{index}/comments").json()
# ==================== Webhook 操作 ====================
def list_hooks(self, owner: str, repo: str) -> List[Dict]:
"""列出仓库的 Webhook"""
return self._request("GET", f"/repos/{owner}/{repo}/hooks").json()
def create_hook(self, owner: str, repo: str, config: Dict, events: List[str],
active: bool = True) -> Dict:
"""创建 Webhook"""
data = {
"type": "gitea",
"config": config,
"events": events,
"active": active
}
return self._request("POST", f"/repos/{owner}/{repo}/hooks", json=data).json()
def delete_hook(self, owner: str, repo: str, hook_id: int) -> None:
"""删除 Webhook"""
self._request("DELETE", f"/repos/{owner}/{repo}/hooks/{hook_id}")
# ==================== Deploy Key 操作 ====================
def list_deploy_keys(self, owner: str, repo: str) -> List[Dict]:
"""列出仓库的 Deploy Key"""
return self._request("GET", f"/repos/{owner}/{repo}/keys").json()
def create_deploy_key(self, owner: str, repo: str, title: str, key: str,
read_only: bool = True) -> Dict:
"""创建 Deploy Key"""
data = {
"title": title,
"key": key,
"read_only": read_only
}
return self._request("POST", f"/repos/{owner}/{repo}/keys", json=data).json()
def delete_deploy_key(self, owner: str, repo: str, key_id: int) -> None:
"""删除 Deploy Key"""
self._request("DELETE", f"/repos/{owner}/{repo}/keys/{key_id}")
# ==================== 标签操作 ====================
def list_tags(self, owner: str, repo: str, page: int = 1, limit: int = 50) -> List[Dict]:
"""列出仓库的标签"""
return self._request("GET", f"/repos/{owner}/{repo}/tags",
params={"page": page, "limit": limit}).json()
def create_tag(self, owner: str, repo: str, tag_name: str, target: str,
message: str = "", **kwargs) -> Dict:
"""创建标签"""
data = {
"tag_name": tag_name,
"target": target,
"message": message
}
data.update(kwargs)
return self._request("POST", f"/repos/{owner}/{repo}/tags", json=data).json()
# ==================== 搜索操作 ====================
def search_repos(self, query: str, page: int = 1, limit: int = 50, **kwargs) -> List[Dict]:
"""搜索仓库"""
params = {"q": query, "page": page, "limit": limit}
params.update(kwargs)
return self._request("GET", "/repos/search", params=params).json().get("data", [])
def search_issues(self, owner: str, repo: str, query: str,
page: int = 1, limit: int = 50) -> List[Dict]:
"""搜索 Issue"""
params = {"q": query, "page": page, "limit": limit}
return self._request("GET", f"/repos/{owner}/{repo}/issues/search", params=params).json()
# ==================== 团队操作 ====================
def list_org_teams(self, org: str) -> List[Dict]:
"""列出组织的团队"""
return self._request("GET", f"/orgs/{org}/teams").json()
def create_team(self, org: str, name: str, permission: str = "read", **kwargs) -> Dict:
"""创建团队"""
data = {"name": name, "permission": permission}
data.update(kwargs)
return self._request("POST", f"/orgs/{org}/teams", json=data).json()
def add_team_member(self, org: str, team: str, username: str) -> None:
"""添加团队成员"""
self._request("PUT", f"/orgs/{org}/teams/{team}/members/{username}")
def remove_team_member(self, org: str, team: str, username: str) -> None:
"""移除团队成员"""
self._request("DELETE", f"/orgs/{org}/teams/{team}/members/{username}")
def add_team_repo(self, org: str, team: str, owner: str, repo: str) -> None:
"""添加团队仓库"""
self._request("PUT", f"/orgs/{org}/teams/{team}/repos/{owner}/{repo}")
def remove_team_repo(self, org: str, team: str, owner: str, repo: str) -> None:
"""移除团队仓库"""
self._request("DELETE", f"/orgs/{org}/teams/{team}/repos/{owner}/{repo}")
def main():
"""命令行入口"""
import argparse
parser = argparse.ArgumentParser(description="Gitea API Client")
parser.add_argument("--url", default=None, help="Gitea URL")
parser.add_argument("--token", default=None, help="API Token")
parser.add_argument("command", help="要执行的命令")
parser.add_argument("args", nargs="*", help="命令参数")
args = parser.parse_args()
client = GiteaClient(url=args.url, token=args.token)
if args.command == "list-repos":
repos = client.list_my_repos()
for r in repos:
print(f"{r['full_name']}: {r.get('description', '')}")
elif args.command == "get-repo":
owner, repo = args.args[0].split("/")
r = client.get_repo(owner, repo)
print(json.dumps(r, indent=2))
elif args.command == "create-repo":
name = args.args[0]
description = args.args[1] if len(args.args) > 1 else ""
r = client.create_repo(name, description)
print(json.dumps(r, indent=2))
elif args.command == "delete-repo":
owner, repo = args.args[0].split("/")
client.delete_repo(owner, repo)
print(f"Repository {owner}/{repo} deleted")
else:
print(f"Unknown command: {args.command}")
parser.print_help()
if __name__ == "__main__":
main()