外观
Git原生Hook结合脚本自动化部署
约 2562 字大约 9 分钟
2026-03-19
小的项目使用git actrun部署都要去创建一个容器太吃资源 也慢,所以使用git原生钩子来做自动化部署
使用的docker部署的gitea
1.映射路径
- 需要把gitea的数据仓库映射出来:
- ./gitea:/data:rw - 把代码和部署的文件夹也映射到容器内
- /volume2/Site:/deploy:rw这样可以在容器内git pull,也可以打包完了复制到部署的站点下
2.找到仓库内的钩子文件
gitea/gitea/git/repositories/{用户名}/{仓库名}.git/hooks/post-receive 可以看到这个脚本作用是去扫描与钩子脚本相同的.d文件(post-receive.d)中的脚本。所以我们只需要在这个文件中放入部署的脚本就可以了
3.容器内预设
# 将仓库设置为git的安全目录
git config --global --add safe.directory /data/git/repositories/yuchen4.编写部署脚本
如果是从当前容器中拉取,当前容器中有这个仓库:可以直接从仓库文件中克隆
deploy.sh
#!/bin/bash
unset GIT_DIR GIT_WORK_TREE
echo "================= 开始部署 ================="
REPO="file:///data/git/repositories/yuchen/vuepress.git"
WORK_REPO_DIR="/deploy/repo/vuepress"
BLOG_DIR="/deploy/blog"
# 目录不存在克隆仓库
if [ ! -d "$WORK_REPO_DIR" ]; then
echo "仓库不存在开始克隆..."
mkdir -p "$WORK_REPO_DIR"
git clone -b release "$REPO" "$WORK_REPO_DIR"
echo "仓库克隆完成"
fi
#设置git hook的git环境
#GIT_GLOBAL="git --work-tree=$WORK_REPO_DIR --git-dir=$WORK_REPO_DIR/.git"
#echo "GIT_GLOBAL: $GIT_GLOBAL"
# 克隆代码
#git clone -b release "$REPO" "$WORK_REPO_DIR"
cd "$WORK_REPO_DIR" || exit 1
echo "==> 工作路径: $(pwd)"
# ==================== 拉代码前后,先记录新旧的 package.json hash ====================
old_hash=$(md5sum package.json pnpm-lock.yaml 2>/dev/null | head -1 | awk '{print $1}')
echo "==== 切换到release并pull ===="
git switch release --force &>/dev/null
git pull --force &>/dev/null
new_hash=$(md5sum package.json pnpm-lock.yaml 2>/dev/null | head -1 | awk '{print $1}')
echo "==> 当前分支: $(git branch)"
#echo "==> 当前仓库地址: $(git remote -v)"
# ==================== 判断是否存在pnpm 不存在就安装 ====================
if ! pnpm --version >/dev/null 2>&1; then
echo "=== pnpm 不存在,开始安装 Node.js + pnpm ==="
sudo apk update
sudo apk add nodejs npm
sudo npm install -g pnpm --registry=https://registry.npmmirror.com
pnpm --version
pnpm i &>/dev/null
else
echo "=== pnpm 已存在,版本:$(pnpm --version) 跳过安装 ==="
fi
# 变化了才重新安装依赖!否则跳过!
if [ "$old_hash" != "$new_hash" ]; then
echo "======== 依赖已变化,重新安装 ========"
pnpm i &>/dev/null
fi
# 限制NODE使用的最大内存(防止内存溢出被kill) && 构建
#export NODE_OPTIONS="--max-old-space-size=512"
echo "run build"
if ! pnpm run build; then
echo "❌ 构建失败,停止部署!"
exit 1
fi
# 发布
rm -rf "$BLOG_DIR"/*
cp -r ./docs/.vuepress/dist/* "$BLOG_DIR"/
echo "================= success! 部署完成 ================="从其他地方拉取:注意钩子环境是非交互式的拉取是不能输入密码需要部署密钥或者git@的方式
deploy.sh
#!/bin/bash
echo "==================== 开始部署 ===================="
WORK_DIR="/deploy/repo/vuepress"
cd "$WORK_DIR" || {
echo "❌ 进入项目目录失败"
exit 1
}
echo -n "工作目录:1"
pwd
chmod -R 777 /deploy/repo/vuepress &>/dev/null
rm -rf ./docs/.vuepress/dist &>/dev/null
# ==================== 设置git hook的git环境 ====================
GIT_GLOBAL="git --work-tree=$WORK_DIR --git-dir=$WORK_DIR/.git"
echo "GIT_GLOBAL: $GIT_GLOBAL"
$GIT_GLOBAL switch release &>/dev/null
# ==================== 拉代码前后,先记录新旧的 package.json hash ====================
old_hash=$(md5sum package.json pnpm-lock.yaml 2>/dev/null | head -1 | awk '{print $1}')
$GIT_GLOBAL pull origin release
new_hash=$(md5sum package.json pnpm-lock.yaml 2>/dev/null | head -1 | awk '{print $1}')
# ==================== 判断是否存在pnpm 不存在就安装 ====================
if ! pnpm --version >/dev/null 2>&1; then
echo "=== pnpm 不存在,开始安装 Node.js + pnpm ==="
apk update
apk add nodejs npm
npm install -g pnpm --registry=https://registry.npmmirror.com
pnpm --version
#第一次默认安装一次依赖
pnpm i
else
echo "=== pnpm 已存在,版本:$(pnpm --version) 跳过安装 ==="
fi
# 变化了才重新安装依赖!否则跳过!
if [ "$old_hash" != "$new_hash" ]; then
echo "==================== 依赖已变化,重新安装 ===================="
pnpm i
fi
pnpm run build
#ls -lha ./docs/.vuepress/dist
#删除以前的站点文件
rm -rf /deploy/blog/*
mv ./docs/.vuepress/dist/* /deploy/blog/
echo "==================== ✅✅✅部署完成✅✅✅ ===================="git原生钩子
服务端:
| 名称 | 触发时机 | 主要作用/使用场景 |
|---|---|---|
pre-receive | 在git push操作到达服务器后,所有引用(refs)被更新之前触发一次。 | 服务端第一个执行的钩子。用于执行全局性的强制策略,例如禁止非快进式推送、进行复杂的访问控制。若脚本非零退出,拒绝所有推送。 |
update | 在pre-receive之后,为每一个即将被更新的引用(分支或标签)各触发一次。 | 用于按分支实施精细化的权限控制。例如,允许特定用户推送 master 分支,但禁止推送 develop 分支。拒绝一个引用不影响其他引用的更新。 |
post-receive | 在git push操作成功完成,所有引用都被更新之后触发一次。 | 主要用于部署和通知。例如,将代码部署到生产环境、触发 CI/CD 流水线、给邮件列表发送通知。此钩子无法中止推送流程。 |
post-update | 在post-receive之后触发,功能与之类似,但通常用于通知。该钩子会收到一个参数,列出所有被更新的引用名。 | 主要用于向外部服务(如网站构建服务)发出通知,告知仓库已更新。 |
push-to-checkout | 当git receive-pack更新了分支,但该分支当前被检出且 receive.denyCurrentBranch 设置为 updateInstead 时触发。 | 用于自定义如何更新当前被检出的分支的工作目录,以便安全地在非裸仓库接收推送。 |
reference-transaction | 当 Git 更新某个引用(ref)时,在事务的特定阶段(提交、中止或准备)触发。 | 一个较底层的钩子,用于高级的引用监听和审计,可以捕获引用的所有更改。 |
客户端:
| 名称 | 触发时机 | 主要作用/使用场景 |
|---|---|---|
applypatch-msg | 由git am命令调用。它接收一个参数,即存放提交信息的临时文件路径。 | 用于检查和规范化补丁提交的提交信息。如果脚本以非零状态退出,git am 将中止。 |
pre-applypatch | 由git am命令调用。在补丁被应用之后、但提交尚未生成之前执行。 | 用于检查应用补丁后的工作区状态,例如运行测试。若脚本非零退出,则补丁不会提交。 |
post-applypatch | 由git am命令调用。在补丁被应用且提交生成之后运行。 | 主要用于通知,例如通知团队成员或补丁作者。此钩子无法影响 git am 命令的执行结果。 |
pre-commit | 在执行git commit时,在获取提交信息并生成提交对象之前触发。可通过--no-verify绕过。 | 最常用的钩子之一。用于检查即将提交的快照,例如运行代码风格检查(Lint)、单元测试、检查调试代码或尾随空白字符等。 |
pre-merge-commit | 在执行git merge时,在合并成功完成后、生成提交信息并提交之前触发。可通过--no-verify绕过。 | 用于在合并提交前对合并结果进行检查,类似于 pre-commit 在合并场景下的应用。 |
prepare-commit-msg | 在执行git commit时,在默认提交信息准备完成后、但编辑器启动之前运行。 | 用于动态编辑提交信息模板。例如,根据分支名自动填充缺陷单号,或从合并信息中提取说明。此钩子不能被 --no-verify 绕过。 |
commit-msg | 在执行git commit或 git merge 时,在用户输入提交信息之后、提交生成之前触发。可通过 --no-verify 绕过。 | 用于验证提交信息是否符合项目规范,例如检查信息格式、长度或是否包含 Signed-off-by 签名。 |
post-commit | 在git commit整个流程成功完成后立即触发。 | 主要用于事后通知,如邮件提醒、更新工单系统。此钩子无法影响 git commit 的结果。 |
pre-rebase | 在执行git rebase之前触发。 | 用于阻止某些分支被变基,例如禁止对已推送到公共仓库的分支进行变基操作。 |
post-checkout | 在git checkout 或 git switch 成功更新工作目录后触发。在 git clone 之后也会执行。 | 用于根据切换后的分支调整工作目录,例如自动切换文档版本、修改环境变量或设置 Git 配置。 |
post-merge | 在git merge或git pull 操作成功完成后触发。 | 用于处理 Git 无法追踪的元数据,例如恢复文件权限、重新生成依赖(如 npm install)。 |
pre-push | 在git push执行期间,远程引用被更新但对象尚未传输时触发。 | 用于在推送前进行最后检查,例如运行尚未提交的本地测试,防止将损坏的或不规范的代码推送到远程仓库。 |
pre-auto-gc | 在执行git gc --auto进行自动垃圾回收之前触发。 | 用于在自动垃圾回收前进行检查或提醒,可以根据情况决定是否中止回收过程。 |
post-rewrite | 被那些会替换已有提交的命令调用,如git commit --amend或git rebase(不包括git filter-branch)。 | 用于处理由重写历史产生的提交变更,其用途类似于 post-checkout 和 post-merge,例如同步相关的元数据。 |
很蠢,改为自定义容器
- 使用dockerfile将依赖(node、npm、pnpm)打包打镜像里去
FROM gitea/gitea:latest
RUN apk add --no-cache nodejs npm && npm install -g pnpm --registry=https://registry.npmmirror.com && rm -rf /root/.npm /tmp/*# 有权限问题 无法用
FROM gitea/gitea:latest-rootless
USER root # 以 root 权限安装依赖(只执行一次)
RUN apk add --no-cache nodejs npm && npm install -g pnpm --registry=https://registry.npmmirror.com && rm -rf /root/.npm /tmp/*
USER git # 切回 git 用户(Gitea 强制要求)#!/bin/bash
unset GIT_DIR GIT_WORK_TREE
echo "================= 开始部署 ================="
REPO="file:///data/git/repositories/yuchen/vuepress.git"
WORK_REPO_DIR="/deploy/repo/vuepress"
BLOG_DIR="/deploy/blog"
# 目录不存在克隆仓库
if [ ! -d "$WORK_REPO_DIR" ]; then
echo "仓库不存在开始克隆..."
mkdir -p "$WORK_REPO_DIR"
git clone -b release "$REPO" "$WORK_REPO_DIR"
echo "仓库克隆完成"
fi
cd "$WORK_REPO_DIR" || exit 1
echo "==> 工作路径: $(pwd)"
# ==================== 拉代码前后,先记录新旧的 package.json hash ====================
old_hash=$(md5sum package.json pnpm-lock.yaml 2>/dev/null | head -1 | awk '{print $1}')
echo "==== 切换到release并pull ===="
git switch release --force &>/dev/null
git pull --force &>/dev/null
new_hash=$(md5sum package.json pnpm-lock.yaml 2>/dev/null | head -1 | awk '{print $1}')
echo "==> 当前分支: $(git branch)"
#echo "==> 当前仓库地址: $(git remote -v)"
echo "pnpm 版本:$(pnpm --version)"
# 变化了才重新安装依赖!否则跳过!
if [ "$old_hash" != "$new_hash" ]; then
echo "======== 依赖已变化,重新安装 ========"
pnpm i &>/dev/null
fi
echo "run build"
if ! pnpm run build; then
echo "❌ 构建失败,停止部署!"
exit 1
fi
# 发布
rm -rf "$BLOG_DIR"/*
cp -r ./docs/.vuepress/dist/* "$BLOG_DIR"/
echo "================= success! 部署完成 ================="