Files
qianshi/deploy.py
T

112 lines
3.9 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.
# -*- coding: utf-8 -*-
"""Step C: 一键部署 — 本地 commit+push → 服务器 git pull 同步
工作流(根治 git 交叉):
本地 git add/commit/push → 服务器 git pull --ff-only
服务器只做 pull 不做 commit —— pull 只移动引用,不产生新 commit。
这是交叉问题的根源修复:旧流程是 SFTP 上传后服务器再 commit 一次,
同一改动被提交两次,author/committer 时间戳不同 → 同 tree 两个 commit ID
→ ahead 1 behind 1。
依赖(已配置,非交互):
本地: credential.helper=store, ~/.git-credentials
服务器: credential.helper=store, ~/.git-credentials, remote → 127.0.0.1:3000
(Gitea 与服务器同机,走 loopback 避免外部网络波动)
用法: python deploy.py "提交信息"
python deploy.py # 用默认信息
"""
import os
import subprocess
import sys
ROOT = os.path.dirname(os.path.abspath(__file__))
os.chdir(ROOT)
# .gitignore 已排除本机脚本与备份目录,git add -A 会自动跳过,无需额外 pathspec。
# (pathspec 排除 ':!x.py' 在文件已被 .gitignore 忽略时会报错:
# "The following paths are ignored by one of your .gitignore files")
DEFAULT_MSG = 'chore: 部署更新'
SSH_BASE = ("ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null "
"-o ConnectTimeout=10 ubuntu@106.55.169.92")
REMOTE_DIR = "/var/www/qianshi"
TIMEOUT = 60
ENV = dict(os.environ, GIT_TERMINAL_PROMPT='0') # 禁凭据交互,缺凭据立即失败而非挂起
def sh(args, timeout=TIMEOUT):
"""跑命令,返回 (exit, stdout)"""
r = subprocess.run(args, capture_output=True, text=True,
timeout=timeout, env=ENV)
return r.returncode, (r.stdout + r.stderr).strip()
def log(msg):
print(' %s' % msg, flush=True)
def remote(cmd, timeout=TIMEOUT):
"""在服务器上执行命令,返回 (exit, stdout)"""
return sh([SSH_BASE, '"cd %s && %s"' % (REMOTE_DIR, cmd)], timeout)
def deploy(msg=DEFAULT_MSG):
log('[1/4] 检查改动')
_, st = sh(['git', 'status', '--porcelain'])
if not st:
log('工作区干净,无需提交')
else:
log('[2/4] 提交')
# .gitignore 已排除本机脚本与备份目录,git add -A 无需额外 pathspec
c, out = sh(['git', 'add', '-A'])
if c:
log('add 失败: %s' % out)
return False
_, staged = sh(['git', 'diff', '--cached', '--name-only'])
if not staged:
log('排除项之外无改动')
else:
log(' %d 个文件' % len(staged.splitlines()))
c, out = sh(['git', 'commit', '-m', msg])
if c:
log('commit 失败: %s' % out)
return False
_, commit = sh(['git', 'rev-parse', '--short', 'HEAD'])
log('本地 HEAD: %s' % commit)
log('[3/4] push origin main')
c, out = sh(['git', 'push', 'origin', 'main'])
if c:
log('push 失败: %s' % out)
return False
log(' pushed')
log('[4/4] 服务器 git pull --ff-only')
c, out = remote('GIT_TERMINAL_PROMPT=0 git pull --ff-only origin main')
if c:
log('pull 失败(若报 diverging,说明服务器产生过本地 commit,需人工处理)')
log(out)
return False
log(' %s' % out.splitlines()[-1] if out else ' ok')
# 校验三端一致
_, local = sh(['git', 'rev-parse', '--short', 'HEAD'])
_, srv = remote('git rev-parse --short HEAD', 30)
srv = srv.strip().splitlines()[-1] if srv else '?'
if local != srv:
log('!! 本地 %s != 服务器 %s,同步失败' % (local, srv))
return False
log('')
log('=== 部署完成 ===')
log('提交: %s %s' % (local, msg))
log('本地/服务器 已同步,工作流无 commit 交叉')
return True
if __name__ == '__main__':
msg = sys.argv[1] if len(sys.argv) > 1 else DEFAULT_MSG
sys.exit(0 if deploy(msg) else 1)