用go实现一个git自动同步服务
[TOC]
本文项目见 git :
https://gitcode.com/qq_34649825/git-sync-service
一、服务功能与背景
1. 核心功能
- 定时将 Windows 本地的 Git 仓库与远端仓库同步(定期执行
git pull,检测并提交本地变更并推送到到远程) - 日志自动管理(临时写入释放、大文件轮转、过期清理)
- 支持作为 Windows 服务或通过任务计划程序运行,实现后台常驻运行,无需手动执行
2. 背景与适用场景
在多设备协作开发、服务器端自动备份等场景中,手动执行 git pull/push 操作不仅繁琐,还可能因遗漏导致代码版本不一致。本工具通过 Go 语言开发,专为 Windows 环境设计,旨在实现 Git 仓库的全自动化同步,解决以下问题:
- 多设备间代码同步不及时导致的协作冲突
- 手动操作易遗忘、效率低的问题
- 无人值守场景下的代码自动备份需求
- 需长期稳定运行的后台同步任务管理
二、服务直接使用方法
1. 先决条件
- Windows 操作系统(Windows Server 2016+ / Windows 10+)
- 已安装并可在
PATH中访问的git - 已配置 SSH 密钥实现与远程仓库的免密连接(见后续详细步骤)
2. 快速获取与编译
2.1 编译可执行文件
在项目目录运行:
1
go build -o git-sync-service.exe main.go
或使用仓库自带脚本:
1
.\build.bat
若修改依赖后请运行:
1
go mod tidy
核心依赖:gopkg.in/ini.v1(配置解析)、github.com/robfig/cron/v3(定时任务)、github.com/kardianos/service(Windows 服务化)。
2.2 配置文件设置
编辑同目录下的 config.ini,设置必要参数:
1
2
3
4
5
6
7
8
9
[Git]
# 本地Git仓库路径(必须是已初始化的Git仓库,含.git目录)
RepoPath = F:/AllMyMarkdowns/gitcode
# 日志文件路径(服务运行日志输出位置)
LogPath = F:/AllMyMarkdowns/autoPushGit_log/git_sync_new.log
# 定时同步表达式(Cron格式,5字段:分 时 日 月 周)
CronExpr = */1 * * * *
# 可选:SSH私钥路径(用于免密连接远程仓库)
PrivateKey = C:/Users/Administrator/.ssh/id_rsa
| 配置项 | 格式要求 | 示例 | 默认值 |
|---|---|---|---|
| RepoPath | 本地绝对路径,需存在.git目录 | D:\Projects\test-repo |
无(必须配置) |
| LogPath | 绝对路径(目录不存在会自动创建) | D:\Logs\git-sync.log |
无(必须配置) |
| CronExpr | Cron标准表达式 | 每5分钟:*/5 * * * *;每天23点:0 23 * * * |
0 * * * *(每小时) |
| PrivateKey | SSH私钥绝对路径 | C:\Users\user\.ssh\id_rsa |
无(可选) |
3. 运行方式选择
3.1 作为 Windows 服务运行
安装(需管理员权限):
1
2
3
.\git-sync-service.exe install
# 或使用脚本:
.\install_service.bat
启动服务:
1
net start GitAutoSyncService
卸载:
1
2
3
.\git-sync-service.exe remove
# 或使用脚本:
.\uninstall_service.bat
注意事项:
- 服务默认以
LocalSystem身份运行,可能无法访问用户级 SSH agent - 若需使用用户凭证,需以指定用户身份安装服务
- 可通过
sc query GitAutoSyncService查看服务状态
3.2 使用任务计划程序(推荐)
仓库中提供了 install_task.bat 脚本(在「任务计划程序」中创建任务 GitAutoSyncTask,触发条件:系统启动与用户登录,运行身份:SYSTEM)。
安装(需管理员权限):
1
.\install_task.bat
删除任务:
1
.\uninstall_task.bat
手动创建任务:
在「任务计划程序」中创建任务,选择以当前用户运行(可使用用户 SSH agent,但需用户登录后才运行)。
4. 日志与排查
- 日志位置:
config.ini中LogPath指定的路径 - 服务启动失败:查看 Windows 事件查看器和日志文件
- Git 凭证问题:以目标账户手动运行
git pull/git push复现并排查错误
5. 两种运行方式对比
| 特性 | 服务方式 | 任务计划方式 |
|---|---|---|
| 无人登录运行 | 支持 | 不支持(依赖用户登录) |
| 账号管理 | 复杂(需配置服务运行身份) | 灵活(可选择当前用户) |
| 安装难度 | 稍高 | 较低 |
| 适用场景 | 服务器无人值守 | 个人电脑脑或需用户凭证场景 |
三、服务详细搭建过程(开发实现)
1. 前置准备
1.1 环境要求
| 依赖项 | 要求 | 说明 |
|---|---|---|
| Go环境 | 1.18+ | 需配置GOROOT、GOPATH环境变量 |
| Git客户端 | 任意稳定版本 | 需添加至系统PATH(支持命令行直接执行git命令) |
| Windows系统 | Windows Server 2016+ / Windows 10+ | 服务化依赖Windows API |
| SSH密钥 | 已配置 | 用于免密连接Git远程仓库(如GitHub、GitLab) |
1.2 SSH 密钥配置(关键步骤)
1.2.1 生成 SSH 密钥
打开命令行(管理员身份),执行以下命令生成密钥(默认路径:C:\Users\用户名\.ssh\):
1
ssh-keygen -t rsa -b 4096 -C "my-email@example.com"
- 执行过程中全部回车(默认无密码,避免服务运行时需要输入密钥密码)
1.2.2 配置远程仓库公钥
- 打开生成的公钥文件:
C:\Users\用户名\.ssh\id_rsa.pub - 复制文件内容(完整字符串,不含换行)
- 登录远程 Git 仓库(如 GitHub),进入「Settings → SSH and GPG keys → New SSH key」
- 粘贴公钥内容,保存即可(实现免密拉取/推送)
1.3 Go 环境配置
1.3.1 环境变量验证
执行以下命令,确保 Go 环境配置正常:
1
2
go version # 输出Go版本(需1.18+)
go env # 查看GOROOT、GOPATH配置
1.3.2 切换 Go 第三方源(解决依赖下载慢/失败)
因默认 Go 模块源(proxy.golang.org)国内访问不稳定,需切换至国内源:
1
2
3
4
# 永久设置国内源(阿里云)
go env -w GOPROXY=https://goproxy.cn,direct
# 允许访问私有仓库(如公司内部Git仓库,可选)
go env -w GOPRIVATE=*.your-company-domain.com
2. 项目结构与文件清单
1
2
3
4
5
6
7
8
9
git-sync-service/
├── main.go # 服务核心代码
├── config.ini # 配置文件
├── install_service.bat # 服务安装脚本
├── uninstall_service.bat # 服务卸载脚本
├── install_task.bat # 任务计划安装脚本
├── uninstall_task.bat # 任务计划卸载脚本
├── build.bat # 编译脚本
└── go.mod # 依赖管理文件
| 文件名 | 作用 | 备注 |
|---|---|---|
main.go |
服务核心代码 | 包含同步逻辑、服务化、日志管理等 |
config.ini |
配置文件 | 需手动创建,配置仓库路径、日志路径等 |
install_service.bat |
服务安装脚本 | 一键安装服务(需管理员身份运行) |
uninstall_service.bat |
服务卸载脚本 | 一键卸载服务(需管理员身份运行) |
install_task.bat/uninstall_task.bat |
任务计划管理脚本 | 用于创建/删除定时任务 |
git-sync-service.exe |
编译后的可执行文件 | 服务运行核心程序 |
3. 核心代码实现思路
如下所有代码以 git 上的为准
3.1 配置加载模块(基于gopkg.in/ini.v1)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 配置结构体定义
type Config {
RepoPath 本地仓库路径
LogPath 日志文件路径
CronExpr 定时任务表达式
PrivateKey SSH私钥路径
ExeDir 程序运行目录
}
// 加载配置文件
func loadConfig(exeDir string) (Config, error) {
1. 检查config.ini是否存在
2. 使用ini库读取配置文件内容
3. 解析并验证各配置项(路径有效性、Cron表达式格式)
4. 处理私钥路径(检查文件存在性,过滤无效路径)
5. 返回结构化配置对象
}
3.2 Git 同步逻辑
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// Git同步主函数
func (s *GitSyncService) syncGit() {
1. 记录同步开始日志
2. 设置同步超时时间(防止无限阻塞)
3. 检查仓库是否为Git仓库(验证.git目录存在)
4. 执行git pull拉取远程变更(调用runGitCmd)
5. 检查本地变更并提交推送(调用checkAndCommit)
6. 处理同步结果(成功/失败/超时日志)
}
// 执行Git命令通用函数
func (s *GitSyncService) runGitCmd(ctx context.Context, cmd string, timeout time.Duration, args ...string) error {
1. 构建完整Git命令(如git pull、git add等)
2. 设置命令运行目录为本地仓库路径
3. 配置Windows隐藏窗口参数(避免闪烁)
4. 若指定私钥,设置GIT_SSH_COMMAND环境变量
5. 执行命令并捕获输出
6. 返回命令执行结果(错误信息包含输出内容)
}
// 检查变更并提交推送
func (s *GitSyncService) checkAndCommit(ctx context.Context) error {
1. 执行git status --porcelain检查本地变更
2. 若未检测到变更,刷新Git索引后再次检查
3. 若存在变更:
a. 执行git add .暂存所有变更
b. 生成包含时间戳的自动提交信息
c. 执行git commit提交变更
d. 执行git push推送到远程仓库
4. 返回执行过程中的错误信息
}
3.3 定时任务(基于github.com/robfig/cron/v3)
1
2
3
4
5
6
7
8
9
10
11
import (
"github.com/robfig/cron/v3"
)
// 启动定时任务
func startCron(cfg *Config, log *Logger) {
1. 初始化cron调度器
2. 添加定时任务(使用配置中的Cron表达式)
3. 任务执行逻辑:调用Git同步函数并记录日志
4. 启动调度器
}
3.4 Windows服务化(基于github.com/kardianos/service)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 服务核心结构体
type GitSyncService struct {
cron 定时任务实例
ctx 上下文对象(用于控制服务生命周期)
cancel 取消函数
wg 等待组(用于优雅关闭)
}
// 服务启动逻辑
func (s *GitSyncService) Start(service service.Service) error {
1. 记录服务启动日志
2. 初始化上下文和等待组
3. 延迟初始化定时任务(避免服务启动阻塞)
4. 添加同步任务到cron调度器(包含配置的Cron表达式)
5. 启动定时任务调度器
}
// 服务停止逻辑
func (s *GitSyncService) Stop(service service.Service) error {
1. 记录服务停止日志
2. 触发上下文取消信号
3. 停止定时任务调度器
4. 等待所有同步任务完成
5. 记录服务已停止日志
}
3.5 日志轮转与过期清理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 日志管理任务(定时执行)
func startLogManageTask() {
1. 创建每小时执行一次的定时器
2. 循环执行:
a. 调用rotateLog()进行日志轮转
b. 调用deleteExpiredLogs()清理过期日志
c. 记录管理过程中的错误信息
}
// 日志轮转函数
func rotateLog() error {
1. 检查当前日志文件大小
2. 若超过最大限制:
a. 将当前日志重命名为带时间戳的历史文件
b. 创建新的空日志文件
3. 返回执行过程中的错误
}
// 删除过期日志文件
func deleteExpiredLogs() error {
1. 遍历日志目录下的文件
2. 筛选出符合命名规则的历史日志文件
3. 删除超过保留期限的日志文件
4. 返回执行过程中的错误
}
4. 编译与部署步骤
4.1 下载依赖
进入项目目录,执行以下命令下载第三方依赖:
1
2
3
4
5
6
# 下载配置解析依赖
go get gopkg.in/ini.v1
# 下载定时任务依赖
go get github.com/robfig/cron/v3
# 下载Windows服务化依赖
go get github.com/kardianos/service
4.2 编译可执行文件
1
go build -o git-sync-service.exe main.go
- 编译成功后,目录下会生成
git-sync-service.exe文件(大小约5-10MB) - 若编译报错,检查:① Go版本是否达标;② 依赖是否下载完成;③ 代码无语法错误
5. 常见问题排查
5.1 服务安装失败
- 可能原因:未以管理员身份运行脚本、服务名冲突、可执行文件缺失
- 解决步骤:
- 重新以管理员身份运行安装脚本
- 若提示“服务已存在”,先执行卸载脚本再安装
- 检查
git-sync-service.exe是否存在于脚本同级目录
5.2 服务启动后不同步
- 可能原因:Cron 表达式错误、仓库路径错误、SSH 密钥配置错误、网络问题
- 解决步骤:
- 查看日志文件(LogPath 配置路径),搜索 “sync failed” 或错误信息
- 验证 Git 仓库路径:
cd RepoPath && git status - 验证 SSH 连接:
ssh -T git@github.com(GitHub 示例) - 验证 Cron 表达式:使用在线工具(如 https://crontab.guru/)检查格式
5.3 Git 拉取/推送失败
- 可能原因:远程仓库地址变更、存在冲突、SSH 密钥错误、Git 未添加至 PATH
- 解决步骤:
- 检查远程地址:
git remote -v - 手动执行
git pull解决冲突 - 验证SSH密钥:
ssh -i 私钥路径 git@远程仓库地址 - 验证Git命令:
git --version
- 检查远程地址:
6. 服务管理与维护
6.1 手动管理命令
1
2
3
4
5
6
7
8
9
10
11
# 启动服务
net start GitAutoSyncService
# 停止服务
net stop GitAutoSyncService
# 查看服务状态
sc query GitAutoSyncService
# 强制删除服务(适用于卸载脚本失败时)
sc delete GitAutoSyncService
6.2 代码更新步骤
- 修改
main.go代码(如调整同步逻辑、日志规则) - 重新编译:
go build -o git-sync-service.exe main.go - 停止服务:
net stop GitAutoSyncService - 替换旧的
git-sync-service.exe文件 - 启动服务:
net start GitAutoSyncService
6.3 配置更新步骤
- 编辑
config.ini文件(修改仓库路径、Cron 表达式等) - 停止服务:
net stop GitAutoSyncService - 启动服务:
net start GitAutoSyncService(配置仅在服务启动时加载)
总结
本服务通过 Go 语言实现 Windows 环境下的 Git 自动同步,核心优势在于:
- 自动化:无需手动操作,定时完成拉取/提交/推送
- 稳定性:服务化运行,后台常驻,崩溃可自动重启
- 灵活性:支持自定义同步频率、日志路径、仓库路径
- 无占用:日志采用临时写入模式,可随时操作日志文件
按照文档配置后,即可实现代码的全自动化同步,适用于多设备协作、服务器自动备份等场景。若遇到问题,优先查看日志文件中的错误信息,或参考「常见问题排查」章节。
TODO
- bug:2025年11月29日9:40 在单位电脑上服务还在后台,但是停止 git 自动同步了,日志没有内容,不知道为啥。