Git 原生机制决定了克隆者拥有完整副本,因此无法直接对仓库内特定目录设置不同用户的读写权限。要实现目录级安全控制,必须依赖 Git 服务器端工具(如 Gitolite 的 VREF 插件)或将敏感目录拆分为独立子模块仓库。
先说结论:Git 本身无法限制目录权限,需通过服务器端配置或仓库拆分实现。
- 读权限隔离:必须将敏感目录拆分为独立仓库或子模块(Submodule)。
- 写权限保护:可使用 Gitolite 的 VREF 路径规则限制特定目录的推送。
- 历史清理:若敏感文件已提交,需使用
git filter-repo彻底清除历史记录。
核心原理与限制
Git 是分布式版本控制系统,每次克隆都会下载完整的历史记录和文件内容。权限控制通常由托管平台(如 GitLab、Gitea)或自建服务器工具(如 Gitolite)在服务器端拦截请求实现,而非 Git 客户端命令。
注意:服务器端路径权限控制(如 Gitolite VREF)通常仅限制推送(Write),无法阻止拥有仓库读取权限的用户克隆后查看文件内容。若需隐藏文件内容,必须物理拆分仓库。
方案一:子模块拆分(实现读写隔离)
将敏感目录迁移为独立仓库,并通过子模块引用。这样可以在独立仓库上设置单独的访问权限。
操作步骤:
- 创建独立仓库:将敏感目录初始化为新仓库并推送到服务器。
- 添加子模块:在主仓库中执行以下命令:
git submodule add git@example.com:secret-project.git path/to/secret
git commit -m "Add secret submodule"
- 配置权限:在 Git 托管平台上,仅授权特定人员访问
secret-project仓库,主仓库权限可放宽。 - 初始化更新:其他成员克隆主仓库后,需单独授权才能更新子模块:
git submodule update `--init`
方案二:Gitolite VREF 配置(实现写保护)
若使用自建 Gitolite 服务器,可通过 VREF/NAME/ 语法限制特定路径的推送权限。此方案适用于防止误提交,但不隐藏已存在的数据。
配置示例(gitolite.conf):
repo myproject
RW+ = @admin
RW = @dev
- VREF/NAME/^confidential/ = @dev
RW = @others
配置说明:
VREF/NAME/^confidential/:匹配以confidential/开头的路径。-:表示拒绝权限。- 上述配置允许
@dev推送大部分代码,但禁止推送confidential/目录下的更改。
敏感信息历史清理
如果敏感文件曾经被提交过,即使删除了文件,历史记录中仍然存在。必须重写历史才能彻底清除。
操作步骤:
- 安装工具:推荐使用
git-filter-repo(比git filter-branch更安全高效)。 - 执行清理:在仓库根目录运行以下命令移除特定路径:
git filter-repo `--path` path/to/secret `--invert-paths`
- 强制推送:清理完成后,需强制推送到远程仓库(注意这会改写历史,需通知团队成员):
git push `--force` `--all`
验证步骤
- 验证子模块权限:使用无敏感目录权限的账号克隆主仓库,执行
git submodule update `--init`,确认是否因权限不足而失败。 - 验证 Gitolite 规则:使用受限账号尝试向受限目录提交并推送,确认服务器是否返回
deny错误。 - 验证历史清理:克隆仓库后,使用
git log `--all` `--full-history` -- path/to/secret确认是否还能查到相关提交记录。
常见坑
- .gitignore 不是权限控制:它只防止文件被提交,不防止已提交文件被读取。若文件已入库,必须清理历史。
- chmod 命令无效:本地
chmod或git update-index `--chmod`只影响本地文件模式,不影响远程用户的访问权限。 - Gitolite 读权限限制:Gitolite 的路径规则主要限制写操作。若用户有仓库读权限,仍可克隆看到所有文件,需配合子模块实现读隔离。
- 强制推送风险:清理历史后强制推送会打断团队成员的开发,需提前通知并协调重新克隆。
参考文档
- Git Official Documentation: git-scm.com
- Gitolite VREF Documentation: gitolite.com
- git-filter-repo Manual: github.com/newren/git-filter-repo