裸仓库不能直接 checkout 分支,因其无工作目录,执行 git checkout 会报 fatal: this operation must be run in a work tree;它仅存储 .git 内容,不包含源码文件,所有文件操作须通过 post-receive 钩子配合 --work-tree 和 --git-dir 显式驱动。

裸仓库本身不支持直接检出分支,所以「分支部署」不是裸仓库的原生能力——它必须靠 post-receive 钩子 + GIT_WORK_TREE + 显式指定分支逻辑来实现。
为什么裸仓库不能直接 checkout 分支
裸仓库(git init --bare)没有工作目录,git checkout 命令会报错:fatal: this operation must be run in a work tree。所有文件操作(比如部署代码)都必须通过外部路径和环境变量驱动。
- 裸仓库只存
.git内容,不包含任何源码文件 -
git push到裸仓库后,仅更新 refs(如refs/heads/main),不会触发任何文件写入 - 想让某分支内容落地到磁盘,必须手动或通过钩子调用
git --work-tree=... --git-dir=... checkout -f <branch></branch>
post-receive 中按分支部署的关键写法
默认的 post-receive 脚本只处理当前推送的 ref,但你得自己解析它,否则只会固定部署一个分支(比如总是 main)。
- 脚本开头要读取 stdin 的 ref 更新流:
while read oldrev newrev refname; do - 用
refname提取分支名:branch=$(echo $refname | sed 's/refs\/heads\///') - 为不同分支设置不同部署路径,例如:
case $branch in main) deploy_path="/var/www/prod";; develop) deploy_path="/var/www/staging";; *) exit 0;; esac - 执行部署时必须同时指定
--git-dir和--work-tree:git --git-dir=/srv/git/myapp.git --work-tree=$deploy_path checkout -f $branch
常见部署失败原因与绕过方式
即使钩子脚本语法正确,也常因权限、路径或 Git 版本行为差异失败。
-
Permission denied:确保运行钩子的用户(通常是 SSH 登录用户)对$deploy_path有写权限,且git命令在该用户 PATH 中 -
error: unable to index file ... Permission denied:部署路径父目录需有g+w权限,或改用sudo -u www-data git ...(不推荐,应统一用户) -
fatal: Not a git repository:漏写了--git-dir,或路径写错(注意裸仓库路径结尾是.git,不是项目名) - Git 2.15+ 默认禁用
checkout -f对非当前分支的操作,需加--force或改用git restore --worktree --staged --source=HEAD -- .
多分支部署时容易被忽略的细节
分支名匹配、钩子执行上下文、以及 Git 的内部状态,三者稍有偏差就会导致部署静默失败。
-
refname是完整 ref 路径(如refs/heads/feature/login),别直接当分支名用 -
post-receive在裸仓库根目录执行,但pwd不一定等于/srv/git/myapp.git,建议所有路径用绝对路径 - 如果部署路径已有文件,
checkout -f会覆盖,但不会清理已删文件;需要额外加git clean -fd(慎用) - 钩子中不要依赖 shell 的交互式配置(如
~/.bashrc),它通常不加载;环境变量要显式设置,比如export GIT_DIR=/srv/git/myapp.git











