Git 裸仓库 + 钩子自动部署:以 Hexo 博客为例
很多人部署个人博客时,会羡慕「本地 git push,服务器自动更新上线」的体验。其实不需要 Jenkins、也不需要 GitHub Actions,只用 Git 自带的 裸仓库(bare repository) 加 钩子(hooks) 就能实现。本文从概念讲清楚,再以 Hexo 博客为例完整实战一遍。
一、什么是 Git 裸仓库
普通仓库(我们平时 git clone 下来的那种)由两部分组成:
- 工作区(working tree):你能看到、能编辑的源文件;
.git目录:Git 的版本数据库,记录所有提交、分支、对象。
而 裸仓库只有后者——它没有工作区,整个仓库本身就是 .git 的内容。所以你进到裸仓库里,看不到 index.md、_config.yml 这些源文件,只能看到 HEAD、objects/、refs/、hooks/ 这些 Git 内部结构。
裸仓库一般以 .git 结尾命名(如 blog.git),创建命令是:
1 | |
它的定位是「只负责存储和接收推送的中转站」,专门给多人/多端推送拉取用,因此不需要、也不应该有工作区(有工作区的仓库被 push 时容易产生冲突)。
二、裸仓库 vs 普通仓库 vs Gitea/GitLab
这三者经常被混在一起,其实是不同层次的东西:
| 维度 | 普通仓库 | 裸仓库 | Gitea / GitLab |
|---|---|---|---|
| 有无工作区 | ✅ 有源文件 | ❌ 只有 Git 数据库 | 底层用裸仓库存储 |
| 主要用途 | 本地开发、编辑 | 接收推送的中转/部署点 | 完整的代码托管平台 |
| 访问方式 | 本地文件系统 | SSH / 文件路径 | Web UI + HTTP/SSH |
| 附带功能 | 无 | 无(仅钩子) | 网页浏览、权限管理、Issue、PR、CI/CD、Wiki… |
| 资源占用 | 低 | 极低 | 较高(需常驻服务、数据库) |
一句话概括:
- 普通仓库是你干活的地方;
- 裸仓库是一个轻量的「服务端仓库」,只做存储和接收;
- Gitea / GitLab 是在裸仓库之上套了一整套 Web 平台(用户系统、网页界面、CI、协作功能)。它们底层存储的,本质上也是一个个裸仓库。
所以如果你只是想「一个人、一个博客、push 上去就发布」,根本不必装 Gitea/GitLab——它们是重型平台,会常驻进程、占内存(在 serv00 这种限制 512M 内存的免费主机上甚至跑不起来)。一个裸仓库 + 钩子,零额外依赖,足矣。
三、什么是 Git Hooks(钩子)
钩子是 Git 在特定事件发生时自动执行的脚本。它们存放在仓库的 hooks/ 目录里,文件名即事件名。只要文件可执行(chmod +x),Git 在对应时机就会调用它。
钩子分两大类:
客户端钩子(在本地触发)
| 钩子 | 触发时机 |
|---|---|
pre-commit |
提交前(常用于代码检查、格式化、跑测试) |
commit-msg |
校验提交信息格式 |
pre-push |
推送前 |
服务端钩子(在接收推送的仓库触发)
| 钩子 | 触发时机 |
|---|---|
pre-receive |
收到推送、写入引用之前(可整体拒绝) |
update |
每个分支引用更新前(逐个分支判断) |
post-receive |
推送完成之后(最常用于自动部署) |
我们实现「push 即部署」靠的就是服务端的 post-receive:它在推送成功落地后执行,正适合用来触发构建和发布。
默认情况下
hooks/目录里全是.sample示例文件,去掉.sample后缀并加上可执行权限即可启用。
四、在服务器上搭建裸仓库接收本地推送
假设服务器用户为 git,下面以 SSH 推送为例。
1. 服务器端:创建裸仓库
1 | |
执行后 ~/repo/blog.git 里就是裸仓库结构(HEAD、config、objects/、refs/、hooks/)。
2. 本地:添加远程并推送
1 | |
端口默认 22 可省略。serv00 等主机的 SSH 端口不是 22,记得带上。能正常 push,说明裸仓库已经能接收推送了。
此时推送只是把代码存进了裸仓库,还不会自动部署——下一步交给钩子。
五、关键问题:裸仓库里看不到源文件,怎么编译发布?
这是最容易卡住的地方。裸仓库没有工作区,目录里全是 Git 对象,自然没法直接 hexo generate。
答案是:钩子在执行时,把推送上来的内容「检出(checkout)」到另一个临时工作目录,再在那里编译。
核心命令是给 git checkout 显式指定两个路径:
1 | |
--git-dir:到哪里读 Git 数据(裸仓库);--work-tree:把文件还原到哪个目录(临时工作区)。
这样就「凭空」在工作目录里得到了完整的源文件,后面 npm install、hexo generate 就和本地一模一样了。生成的静态文件再复制到 Nginx 的站点目录,就完成了发布。
整个数据流是:
1 | |
六、实战:Hexo 的 post-receive 钩子
在服务器上编辑裸仓库的钩子文件:
1 | |
写入以下内容(按自己的路径修改变量):
1 | |
赋予可执行权限:
1 | |
完成后,本地每次:
1 | |
服务器就会自动检出源码、hexo generate、把成品推到站点目录,几秒后博客即更新上线。
几个实战要点
unset GIT_DIR必不可少。post-receive 运行时环境里已有GIT_DIR指向裸仓库,不清掉的话cd进工作目录后的 git 命令会全部指向错误位置,导致构建失败。checkout -f的-f表示强制覆盖工作目录,保证每次都是干净的最新源码。npm install别只装生产依赖。Hexo 的渲染器、主题等很多在devDependencies里,用--production=false(或直接npm install)才能装全。首次较慢,之后有缓存会快。- 复制用
public/.而不是public/*,可以连同隐藏文件一起复制。 - 站点目录与 Nginx 对应。
PUBLISH_DIR必须是 Nginx 配置中root指向的目录。 - 想看部署日志,可在脚本里把输出重定向到日志文件,或推送时直接观察终端回显(post-receive 的 stdout 会回传给本地终端)。
七、和 GitHub Actions / Gitea 比一比
| 方案 | 适合场景 | 成本 |
|---|---|---|
| 裸仓库 + post-receive | 单人、单机部署,想要极简 | 几乎为零,无常驻进程 |
| GitHub Actions | 代码托管在 GitHub,想要云端构建 | 免费额度内零成本,依赖 GitHub |
| Gitea / GitLab | 团队协作、需要网页/权限/Issue/CI | 需常驻服务,吃内存 |
对个人 Hexo 博客、尤其是部署在 serv00 这类资源受限的免费主机上,裸仓库 + 钩子是性价比最高的方案:没有第三方依赖、不占常驻进程、推送即发布。
小结
- 裸仓库是没有工作区、只存 Git 数据库的服务端仓库,专门用来接收推送;
- 它和普通仓库的区别在于「有没有工作区」,和 Gitea/GitLab 的区别在于「是不是一整套平台」;
- 钩子是 Git 在特定事件自动执行的脚本,
post-receive用于推送后自动部署; - 裸仓库虽看不到源文件,但钩子能用
git --work-tree=... checkout把源码检出到临时目录,再编译发布; - 对 Hexo 而言,一段 post-receive 脚本就能实现
git push即自动hexo generate并上线。
把 CI/CD 想得太重,往往会忽略 Git 自带的这套轻巧机制。对个人项目来说,它简单、可靠、零成本。