d73ff9b0fb
Deploy Site / deploy-vercel (push) Has been cancelled
Deploy Site / deploy-docs (push) Has been cancelled
Docker / shell lint / Lint Dockerfile (hadolint) (push) Has been cancelled
Docker / shell lint / Lint docker/ shell scripts (shellcheck) (push) Has been cancelled
Docker Build and Publish / build-amd64 (push) Has been cancelled
Docker Build and Publish / build-arm64 (push) Has been cancelled
Lint (ruff + ty) / ruff + ty diff (push) Has been cancelled
Lint (ruff + ty) / ruff enforcement (blocking) (push) Has been cancelled
Lint (ruff + ty) / Windows footguns (blocking) (push) Has been cancelled
Nix Lockfile Fix / auto-fix-main (push) Has been cancelled
Nix Lockfile Fix / fix (push) Has been cancelled
Nix / nix (macos-latest) (push) Has been cancelled
Nix / nix (ubuntu-latest) (push) Has been cancelled
OSV-Scanner / Scan lockfiles (push) Has been cancelled
Build Skills Index / build-index (push) Has been cancelled
Tests / test (1) (push) Has been cancelled
Tests / test (2) (push) Has been cancelled
Tests / test (3) (push) Has been cancelled
Tests / test (4) (push) Has been cancelled
Tests / test (5) (push) Has been cancelled
Tests / test (6) (push) Has been cancelled
Tests / e2e (push) Has been cancelled
uv.lock check / uv lock --check (push) Has been cancelled
Docker Build and Publish / merge (push) Has been cancelled
Build Skills Index / trigger-deploy (push) Has been cancelled
Tests / save-durations (push) Has been cancelled
181 lines
5.8 KiB
Python
181 lines
5.8 KiB
Python
#!/usr/bin/env python3
|
|
"""分段推送到 Gitea:用 /contents API 分批上传文件
|
|
|
|
策略:
|
|
1. 每批上传最多 20MB 的文件,控制在 nginx 的 ~30MB 限制内
|
|
2. 按目录结构分批上传,保持提交历史相对整洁
|
|
3. 小文件合并到同一 commit,大文件单独 commit
|
|
"""
|
|
|
|
import os, base64, json, subprocess, sys, time
|
|
|
|
GITEA_URL = "https://git666.u7f.cn"
|
|
OWNER = "sawz"
|
|
REPO = "hermes-agent"
|
|
AUTH = "Basic c2F3ejo0NTMyNDYyMnd3"
|
|
|
|
MAX_BATCH_SIZE = 20 * 1024 * 1024 # 20MB 批次上限(留余量)
|
|
|
|
def run_curl(method, path, data=None):
|
|
"""执行 curl 请求"""
|
|
url = f"{GITEA_URL}{path}"
|
|
cmd = ["curl", "-s", "-w", "\n%{http_code}", "-X", method, url,
|
|
"-H", f"Authorization: {AUTH}",
|
|
"-H", "Content-Type: application/json"]
|
|
if data:
|
|
cmd.extend(["-d", json.dumps(data)])
|
|
|
|
for attempt in range(3):
|
|
try:
|
|
result = subprocess.run(cmd, capture_output=True, text=True, timeout=60)
|
|
lines = result.stdout.strip().split("\n")
|
|
if len(lines) < 2:
|
|
return None, 0
|
|
http_code = int(lines[-1])
|
|
body = "\n".join(lines[:-1])
|
|
if body:
|
|
return json.loads(body), http_code
|
|
return {}, http_code
|
|
except Exception as e:
|
|
if attempt < 2:
|
|
time.sleep(2)
|
|
continue
|
|
print(f" API 错误: {e}")
|
|
return None, 0
|
|
|
|
def get_last_sha():
|
|
"""获取当前 main 分支的最新 commit SHA"""
|
|
resp, code = run_curl("GET", f"/api/v1/repos/{OWNER}/{REPO}/branches/main")
|
|
if resp and "commit" in resp:
|
|
return resp["commit"]["id"]
|
|
return None
|
|
|
|
def upload_file(local_path):
|
|
"""上传单个文件到 Gitea"""
|
|
repo_path = os.path.relpath(local_path, repo_dir)
|
|
if repo_path.startswith("./"):
|
|
repo_path = repo_path[2:]
|
|
|
|
with open(local_path, "rb") as f:
|
|
content = base64.b64encode(f.read()).decode()
|
|
|
|
# 检查文件大小
|
|
file_size = len(content) * 3 // 4 # base64 膨胀约 1/3
|
|
if file_size > MAX_BATCH_SIZE:
|
|
print(f" ⚠ 文件过大 ({file_size/1024/1024:.1f}MB): {repo_path}")
|
|
return None
|
|
|
|
data = {
|
|
"content": content,
|
|
"message": f"add {repo_path}",
|
|
"branch": "main"
|
|
}
|
|
|
|
resp, code = run_curl("PUT", f"/api/v1/repos/{OWNER}/{REPO}/contents/{repo_path}", data)
|
|
|
|
if code == 201:
|
|
return resp.get("commit", {}).get("sha", "")
|
|
elif code == 404:
|
|
print(f" ✗ 仓库不存在")
|
|
return None
|
|
elif code == 413:
|
|
print(f" ✗ 413 太大: {repo_path} ({file_size/1024/1024:.1f}MB)")
|
|
return None
|
|
else:
|
|
# 可能是文件已存在
|
|
return None
|
|
|
|
def get_all_files(repo_root):
|
|
"""递归获取所有需要上传的文件"""
|
|
files = []
|
|
exclude_dirs = {".git", "__pycache__", ".venv", ".vscode",
|
|
".hermes-docker", ".notebooklm-home", ".notebooklm-cli-venv",
|
|
".notebooklm-playwright", ".pip-cache", ".uv-cache", "venv"}
|
|
exclude_files = {".DS_Store"}
|
|
|
|
for root, dirs, filenames in os.walk(repo_root):
|
|
dirs[:] = [d for d in dirs if d not in exclude_dirs]
|
|
for f in filenames:
|
|
if f in exclude_files or f.endswith(".pyc"):
|
|
continue
|
|
filepath = os.path.join(root, f)
|
|
filesize = os.path.getsize(filepath)
|
|
files.append((filepath, filesize))
|
|
|
|
return files
|
|
|
|
def main():
|
|
global repo_dir
|
|
repo_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
|
|
# 先确保仓库和 main 分支存在
|
|
resp, code = run_curl("GET", f"/api/v1/repos/{OWNER}/{REPO}")
|
|
if not resp or "id" not in resp:
|
|
print("仓库不存在!")
|
|
sys.exit(1)
|
|
|
|
# 检查 main 分支
|
|
branch, _ = run_curl("GET", f"/api/v1/repos/{OWNER}/{REPO}/branches/main")
|
|
if not branch:
|
|
# 用 README 初始化
|
|
print("初始化仓库 (README)...")
|
|
readme_b64 = base64.b64encode(b"# Hermes Agent\n\nBackup mirror").decode()
|
|
data = {
|
|
"content": readme_b64,
|
|
"message": "Initial commit",
|
|
"branch": "main"
|
|
}
|
|
resp, code = run_curl("PUT", f"/api/v1/repos/{OWNER}/{REPO}/contents/README.md", data)
|
|
if code == 201:
|
|
print(" 初始化成功")
|
|
else:
|
|
print(f" 初始化失败: {code}")
|
|
sys.exit(1)
|
|
|
|
# 获取文件列表
|
|
all_files = get_all_files(repo_dir)
|
|
total_files = len(all_files)
|
|
total_size = sum(sz for _, sz in all_files)
|
|
print(f"共 {total_files} 个文件, {total_size/1024/1024:.1f}MB")
|
|
|
|
# 分批上传
|
|
uploaded = 0
|
|
skipped = 0
|
|
batch_files = []
|
|
batch_size = 0
|
|
|
|
for filepath, filesize in all_files:
|
|
if filesize > MAX_BATCH_SIZE:
|
|
print(f" ⚠ 超大文件跳过: {os.path.relpath(filepath, repo_dir)} ({filesize/1024/1024:.1f}MB)")
|
|
skipped += 1
|
|
continue
|
|
|
|
if batch_size + filesize > MAX_BATCH_SIZE and batch_files:
|
|
# 提交当前批次
|
|
for fp in batch_files:
|
|
result = upload_file(fp)
|
|
if result:
|
|
uploaded += 1
|
|
else:
|
|
skipped += 1
|
|
batch_files = []
|
|
batch_size = 0
|
|
print(f" 进度: {uploaded}/{total_files} (跳过 {skipped})")
|
|
|
|
batch_files.append(filepath)
|
|
batch_size += filesize
|
|
|
|
# 提交最后一批
|
|
if batch_files:
|
|
for fp in batch_files:
|
|
result = upload_file(fp)
|
|
if result:
|
|
uploaded += 1
|
|
else:
|
|
skipped += 1
|
|
|
|
print(f"\n完成!上传 {uploaded}, 跳过 {skipped}")
|
|
|
|
if __name__ == "__main__":
|
|
main()
|