基于Python watchdog实现GitHub repo master分支变更监控与自动同步
基于Python的GitHub仓库Push自动同步Watchdog实现
核心思路
Watchdog类监控的本质是主动定期比对GitHub远端仓库和本地部署仓库的commit差异,不需要服务器暴露公网端口,比Webhook模式更适配自有服务器无公网IP的场景,实现成本极低。
前置准备
- 在自有服务器上提前克隆目标GitHub仓库的master分支,配置Git拉取的免密凭证(可用SSH密钥或GitHub PAT配合credential helper实现,避免pull操作时手动输入账号密码)
- 安装所需Python依赖:
pip install PyGithub schedule - 在GitHub账号下生成拥有目标仓库读取权限的Personal Access Token,配置为服务器环境变量,不要硬编码在脚本中
- 注意:Token需要配置最小权限,只勾选仓库读取权限即可,不要开放多余的写权限
核心逻辑流程
- 脚本启动时,从本地隐藏文件
.last_sync_hash中读取上次成功同步的commit hash值,首次运行时自动读取本地仓库当前的HEAD hash作为初始值,避免启动时无意义的全量拉取 - 按照预设的轮询周期(一般设60-120秒即可),调用GitHub API获取远端master分支的最新commit hash
- 比对远端最新hash和本地记录的同步hash:
- hash一致时,直接进入下一轮等待
- hash不一致时,判定存在新的push操作,触发同步流程
- 同步流程:进入本地仓库目录执行
git pull origin master拉取最新代码,根据部署需求处理本地未提交的修改,拉取成功后将最新的远端commit hash更新写入本地记录文件,同时输出运行日志
可直接运行的核心参考代码
import os import subprocess import time from github import Github import schedule # 配置项 GITHUB_TOKEN = os.getenv("GITHUB_PAT") REPO_FULL_NAME = "你的GitHub用户名/目标仓库名" LOCAL_REPO_PATH = "/opt/deploy/本地对应仓库的绝对路径" LAST_HASH_FILE = os.path.join(LOCAL_REPO_PATH, ".sync_watchdog_hash") POLL_INTERVAL = 120 # 轮询间隔,单位秒 def get_last_synced_hash(): if not os.path.exists(LAST_HASH_FILE): result = subprocess.run( ["git", "-C", LOCAL_REPO_PATH, "rev-parse", "HEAD"], capture_output=True, text=True ) return result.stdout.strip() with open(LAST_HASH_FILE, "r") as f: return f.read().strip() def save_synced_hash(commit_hash): with open(LAST_HASH_FILE, "w") as f: f.write(commit_hash) def check_and_sync(): try: g = Github(GITHUB_TOKEN) repo = g.get_repo(REPO_FULL_NAME) latest_remote_hash = repo.get_branch("master").commit.sha last_synced = get_last_synced_hash() if latest_remote_hash != last_synced: print(f"检测到远端新提交:{latest_remote_hash},开始执行同步") pull_result = subprocess.run( ["git", "-C", LOCAL_REPO_PATH, "pull", "origin", "master", "--force"], capture_output=True, text=True ) if pull_result.returncode == 0: save_synced_hash(latest_remote_hash) print(f"同步完成,当前本地版本hash:{latest_remote_hash}") else: print(f"同步失败,错误信息:{pull_result.stderr}") except Exception as e: print(f"Watchdog运行异常:{str(e)}") if __name__ == "__main__": check_and_sync() schedule.every(POLL_INTERVAL).seconds.do(check_and_sync) while True: schedule.run_pending() time.sleep(1)
说明:如果本地需要保留环境特有的配置修改,不要加
--force参数,可在pull前先执行git stash暂存本地修改,拉取完成后再执行git stash pop恢复修改,避免覆盖本地配置。
运维注意事项
- 将脚本配置为systemd常驻服务,设置开机自启、异常崩溃自动重启,不要仅靠nohup手动拉起
- 轮询间隔不要设置过短,带Token的GitHub接口限流为每小时5000次,按2分钟一次的频率运行全天仅触发720次请求,远低于限流阈值
- 将脚本的输出日志重定向到本地文件,方便后续排查同步失败、网络异常等问题
- 如果后续需要更高的实时性,可以在服务器有公网可达端口的前提下,将轮询逻辑替换为接收GitHub Webhook的触发逻辑,实现push后秒级同步,但无公网IP的场景下轮询Watchdog是最稳定省事的方案
内容的提问来源于stack exchange,提问作者Agosti Alessio
相关产品推荐
相关产品推荐

