一行 Git 配置实现推送即发布,防止报错禁止提交非裸仓库等提示
如果你在服务器上用一个普通仓库(非裸仓库)作为代码目录,然后在本地直接向它推送当前分支,Git 默认会拒绝:
! [remote rejected] main -> main (branch is currently checked out)
原因是:远端仓库的当前分支已经被检出到工作区,如果直接更新分支,工作区文件就会和仓库记录不一致。Git 默认选择拒绝,避免把服务器上的工作区搞乱。
但很多个人项目、内网工具或轻量部署场景里,服务器上的代码目录就是运行目录,而且远端不会修改代码。这时我们想要的其实是:
本地推送成功后,远端仓库自动把工作区同步到最新提交。
实现这个效果,只需要在远端仓库里执行一行命令:
git config receive.denyCurrentBranch updateInstead
这行命令做了什么?
receive.denyCurrentBranch 控制的是:当有人向远端仓库的当前分支推送时,Git 应该怎么办。
默认值相当于 refuse,也就是拒绝。把它设置为 updateInstead 后,Git 会这样处理:
- 检查远端仓库的工作区和索引是否干净;
- 如果干净,就接受推送;
- 接受后,自动把工作区更新到最新提交。
所以它实现了一个非常轻量的“推送即同步”:
本地 git push → 远端仓库更新分支 → 远端工作区自动检出最新代码
注意:这个配置必须写在远端仓库里,不是本地仓库。并且远端必须是普通仓库(git init 出来的,有工作区),裸仓库没有工作区,配置它没有意义。
使用方式
1. 远端仓库准备
假设服务器上的代码目录是:
/srv/www/my-project
它已经是一个普通 Git 仓库,并且当前分支就是你要部署的分支,比如 main。
登录服务器,进入该仓库:
cd /srv/www/my-project
执行配置:
git config receive.denyCurrentBranch updateInstead
这是仓库级配置,只对当前仓库生效。建议不要用 --global,除非你明确知道所有仓库都需要这个行为。
2. 验证配置
在远端仓库执行:
git config --get receive.denyCurrentBranch
应该输出:
updateInstead
3. 本地正常推送
本地仓库执行:
git push origin main
如果远端工作区是干净的,推送会成功,并且服务器上的文件会自动变成最新版本。
如果之前本地和远端历史已经分叉,普通推送仍可能失败。确认远端没有你需要保留的提交后,可以用更安全的强制推送:
git push --force-with-lease origin main
推送成功后,updateInstead 会继续负责把远端工作区更新到最新提交。
4. 如何取消
如果以后不想让远端自动更新工作区,可以在远端仓库执行:
git config receive.denyCurrentBranch refuse
或者删除该配置:
git config --unset receive.denyCurrentBranch
什么时候会需要这行命令?
它适合的是轻量、单点、以服务器目录为发布目标的场景。
适合的场景
- 个人项目或内网工具 :服务器上的目录就是运行目录,代码只从本地推送。
- 没有 CI/CD 的小型部署 :不想为一个小项目搭流水线,也不想写 Git Hook。
- 远端代码只读 :服务器上不会直接改代码,工作区能保持干净。
- 快速原型、家庭实验室、测试环境 :追求简单直接,能接受较弱的部署保障。
- 你想避免写
post-receive钩子 :只需要文件同步,不需要推送后执行构建、重启服务等复杂动作。
一句话:当你把“推送”当作“发布”,并且发布动作只是“把工作区文件更新到最新提交”时,它非常合适。
不适合的场景
- 远端是 GitHub、GitLab、Gitea 等托管平台 :你无法登录远端仓库目录执行这个配置。托管平台上的仓库通常是裸仓库,也没有可自动同步的工作区。
- 多人协作或生产环境要求较高 :强制推送、工作区直接更新、部署非原子等问题会带来风险。
- 服务器上经常手工改代码 :工作区一旦有未提交修改,推送就会被拒绝。
- 需要推送后构建、安装依赖、重启服务 :
updateInstead只负责更新工作区文件,不会帮你执行这些动作,仍然需要 Hook 或 CI/CD。 - 需要原子发布、回滚、灰度等能力 :这不是它的目标。
.git目录可能被公开访问 :普通仓库的工作区里包含.git,如果 Web 服务器能直接访问它,存在源码和历史泄露风险。- 远端 Git 版本低于 2.3 :
updateInstead需要 Git 2.3 及以上版本,建议使用 2.4+。
一个重要的前提:远端工作区必须干净
updateInstead 不是无条件覆盖。它会在接收推送前检查远端工作区状态。如果服务器上有未提交的修改,或者某些未跟踪文件会被推送覆盖,推送会被拒绝。
常见报错类似:
error: refusing to update checked out branch
或者提示工作区有本地修改。
处理方式:
- 服务器上不要直接改代码;
- 把运行时生成的文件加入
.gitignore; - 必要时在服务器上清理或提交本地修改;
- 确保推送不会覆盖服务器上的未跟踪文件。
这也是它适合“远端不会修改代码”场景的原因。
和 post-receive 钩子怎么选?
| 方案 | 优点 | 缺点 | 适合 |
|---|---|---|---|
receive.denyCurrentBranch updateInstead | 一行配置,极简,无需脚本 | 只能同步文件,不能构建/重启;工作区必须干净;非原子部署 | 个人项目、内网、轻量发布 |
post-receive 钩子 | 可执行构建、重启、通知等自定义动作 | 需要写脚本、维护权限 | 自建服务器上更正式的部署 |
| Webhook + 部署服务 | 适合托管平台,可扩展 | 需要额外 HTTP 服务和鉴权 | GitHub/GitLab 等平台 |
如果你的需求只是“推送后服务器文件变成最新”,updateInstead 是最省事的。如果你需要“推送后执行一系列发布动作”,钩子或 CI/CD 更合适。
总结
一行命令:
git config receive.denyCurrentBranch updateInstead
在远端普通仓库执行后,就可以把本地 git push 变成一次轻量发布:推送成功,远端工作区自动同步到最新提交。
它适合远端不修改代码、服务器目录就是运行目录、部署动作只是更新文件的场景。使用前确认三点:
- 远端是普通仓库,不是裸仓库;
- 远端 Git 版本支持
updateInstead; - 远端工作区能保持干净,且你能接受它的安全和部署原子性限制。
命令只有一行,但它背后的约定很明确:服务器目录是发布目标,不是开发目录。
