209 lines
7.2 KiB
Python
209 lines
7.2 KiB
Python
#!/usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
"""一键推送到 Gitea 远程。
|
|
|
|
流程(每步都先预览,要你确认才进下一步):
|
|
1. git status + diff --stat → 让你看要推哪些文件
|
|
2. 检查 .env / secrets 是否会进 git(check-ignore + 内容嗅探)
|
|
3. 检查 APK / 大文件
|
|
4. git add <你指定的文件> → 不 add . / 全量,精准 add
|
|
5. git status 再次确认
|
|
6. git commit -m <你给的 message> → 没 message 就退出
|
|
7. git push origin main → 推
|
|
|
|
退出码:
|
|
0 = 成功推到 origin/main
|
|
1 = 你取消
|
|
2 = 推送失败(网络 / 认证 / 冲突)
|
|
"""
|
|
from __future__ import annotations
|
|
import argparse
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
|
|
REPO_DIR = r"D:\selftools\diary-news"
|
|
REMOTE = "origin"
|
|
BRANCH = "main"
|
|
|
|
|
|
def run(cmd: str, check: bool = True, capture: bool = True) -> subprocess.CompletedProcess:
|
|
"""在仓库目录跑 git 命令。"""
|
|
return subprocess.run(
|
|
cmd, shell=True, cwd=REPO_DIR, check=check,
|
|
capture_output=capture, text=True, encoding="utf-8", errors="replace",
|
|
)
|
|
|
|
|
|
def confirm(prompt: str, default_yes: bool = False) -> bool:
|
|
suffix = "[Y/n]" if default_yes else "[y/N]"
|
|
ans = input(f"{prompt} {suffix}: ").strip().lower()
|
|
if not ans:
|
|
return default_yes
|
|
return ans in ("y", "yes")
|
|
|
|
|
|
def main() -> int:
|
|
ap = argparse.ArgumentParser(description="推送 diary-news 到 Gitea")
|
|
ap.add_argument("--message", "-m", help="commit message(必填,不然 dry-run 后停)")
|
|
ap.add_argument("--files", nargs="*", help="要 add 的文件路径(默认 add 所有 untracked + modified)")
|
|
ap.add_argument("--do-push", action="store_true", default=True,
|
|
help="默认会真推;传 --no-do-push 只 commit 不 push")
|
|
ap.add_argument("--no-do-push", dest="do_push", action="store_false")
|
|
ap.add_argument("--allow-apk", action="store_true", help="允许推 APK(默认会拦)")
|
|
args = ap.parse_args()
|
|
|
|
print(f"==== 仓库: {REPO_DIR} ====")
|
|
print(f"==== 远程: {REMOTE}/{BRANCH} ====\n")
|
|
|
|
# 0) 远程是否同步
|
|
r = run("git rev-parse --abbrev-ref HEAD")
|
|
if r.stdout.strip() != BRANCH:
|
|
print(f"✗ 当前在 {r.stdout.strip()},不在 {BRANCH} 分支,拒绝推")
|
|
return 2
|
|
print(f"✓ 在 {BRANCH} 分支")
|
|
|
|
r = run("git fetch --quiet " + REMOTE)
|
|
if r.returncode != 0:
|
|
print(f"✗ fetch 失败: {r.stderr}"); return 2
|
|
|
|
behind = run(f"git log HEAD..{REMOTE}/{BRANCH} --oneline", capture=True).stdout.strip()
|
|
if behind:
|
|
print(f"✗ 本地落后于 {REMOTE}/{BRANCH},先 pull:")
|
|
print(behind)
|
|
return 2
|
|
print(f"✓ 本地与 {REMOTE}/{BRANCH} 同步")
|
|
|
|
# 1) 当前状态
|
|
print(f"\n==== 1) git status ====")
|
|
print(run("git status --short").stdout.rstrip() or "(无变更)")
|
|
|
|
# 2) 大文件嗅探(> 5MB) — 用 git status --porcelain + Python 拿 size
|
|
# (避开 shell pipe 退出码 255 / xargs 空输入 / Windows 路径带空格)
|
|
print(f"\n==== 2) 大文件嗅探(> 5MB) ====")
|
|
r = run("git status --porcelain", check=False)
|
|
big = [] # [(size_mb, path), ...]
|
|
for line in r.stdout.splitlines():
|
|
if not line.strip():
|
|
continue
|
|
# 格式: "XY filename" — XY 是 2 字符,后面可能 1 空格或更复杂(rename)
|
|
# 取第三个 token 起为路径
|
|
parts = line.split(maxsplit=1)
|
|
if len(parts) < 2:
|
|
continue
|
|
path = parts[1].strip().strip('"')
|
|
if not os.path.exists(path):
|
|
continue
|
|
try:
|
|
size_mb = os.path.getsize(path) / 1024 / 1024
|
|
except OSError:
|
|
continue
|
|
if size_mb > 5:
|
|
big.append((size_mb, path))
|
|
big.sort(key=lambda x: -x[0])
|
|
if big:
|
|
for size_mb, path in big:
|
|
print(f" ⚠ {size_mb:.1f} MB {path}")
|
|
else:
|
|
print(" ✓ 无 > 5MB 文件")
|
|
|
|
if big and not args.allow_apk:
|
|
apk = [p for _, p in big if p.lower().endswith(".apk")]
|
|
if apk:
|
|
print(f"\n ✗ 检测到 APK,默认拒绝推送(传 --allow-apk 强制推)")
|
|
print(f" 建议:把 '{apk[0]}' 加进 .gitignore,或上传到 release page")
|
|
return 1
|
|
|
|
# 3) 敏感文件嗅探(.env / secrets / .key / .pem) — 纯 Python
|
|
print(f"\n==== 3) 敏感文件嗅探 ====")
|
|
sensitive = []
|
|
keywords = (".env", "secret", "password", ".key", ".pem", "credentials")
|
|
r = run("git status --porcelain", check=False)
|
|
for line in r.stdout.splitlines():
|
|
if not line.strip():
|
|
continue
|
|
path = line.split(maxsplit=1)[1].strip().strip('"')
|
|
low = path.lower()
|
|
if any(k in low for k in keywords):
|
|
sensitive.append(path)
|
|
if sensitive:
|
|
for p in sensitive:
|
|
print(f" ⚠ {p}")
|
|
else:
|
|
print(" ✓ 未发现敏感文件(.env / secrets / key / pem)")
|
|
|
|
# 4) 选文件
|
|
print(f"\n==== 4) 选择要 add 的文件 ====")
|
|
if args.files:
|
|
files = args.files
|
|
else:
|
|
# 默认 add 所有 untracked + modified(不包含被 ignore 的)
|
|
r = run("git ls-files --modified --others --exclude-standard")
|
|
files = [f for f in r.stdout.splitlines() if f.strip()]
|
|
if not files:
|
|
print(" (无文件可 add)"); return 1
|
|
print(f" 共 {len(files)} 个:")
|
|
for f in files:
|
|
size_note = ""
|
|
if f.lower().endswith(".apk"):
|
|
size_note = " ← APK!"
|
|
print(f" {f}{size_note}")
|
|
|
|
if not confirm("确认 add 上面这些?"):
|
|
print("已取消"); return 1
|
|
|
|
# 5) git add
|
|
for f in files:
|
|
r = run(f'git add -- "{f}"')
|
|
if r.returncode != 0:
|
|
print(f" ✗ git add {f} 失败: {r.stderr}"); return 2
|
|
print(f" ✓ 已 add {len(files)} 个文件")
|
|
|
|
# 6) 再确认 status
|
|
print(f"\n==== 5) git status(已 staged) ====")
|
|
print(run("git status --short").stdout.rstrip())
|
|
|
|
# 7) diff stat
|
|
print(f"\n==== 6) diff --stat(将 commit 的内容) ====")
|
|
print(run("git diff --cached --stat").stdout.rstrip())
|
|
|
|
# 8) commit
|
|
if not args.message:
|
|
msg = input("commit message (直接回车用 'chore: ...'): ").strip()
|
|
if not msg:
|
|
msg = "chore: push via push_to_gitea.py"
|
|
else:
|
|
msg = args.message
|
|
print(f"\n==== 7) git commit -m \"{msg}\" ====")
|
|
r = run(f'git commit -m "{msg}"')
|
|
if r.returncode != 0:
|
|
# 可能是空 commit 或 hooks 拒
|
|
print(f" ! commit 退出码 {r.returncode}")
|
|
print(r.stdout)
|
|
print(r.stderr)
|
|
return 2
|
|
print(f" ✓ commit 成功")
|
|
print(r.stdout.rstrip())
|
|
|
|
# 9) push
|
|
if not args.push:
|
|
print(f"\n==== 8) --no-push 跳过,只 commit ====")
|
|
print(" 下次手动:git push origin main")
|
|
return 0
|
|
|
|
print(f"\n==== 8) git push {REMOTE} {BRANCH} ====")
|
|
if not confirm(f"确认推 {REMOTE}/{BRANCH}?"):
|
|
print("已取消,commit 已留下但未推"); return 1
|
|
r = run(f"git push {REMOTE} {BRANCH}")
|
|
if r.returncode != 0:
|
|
print(f" ✗ push 失败: {r.stderr}"); return 2
|
|
print(f" ✓ push 成功")
|
|
print(r.stdout.rstrip())
|
|
|
|
print(f"\n🎉 完成!新 commit 已推 {REMOTE}/{BRANCH}")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|