简介:本资源是一份面向中高级Linux系统管理员与DevOps工程师的Git代码协同平台搭建实战指南,聚焦于构建集版本控制(Git)、多仓库管理(Repo)与代码评审(Gerrit)于一体的完整企业级代码服务器。内容覆盖从基础环境准备、Gitosis权限体系配置、Repo manifest仓库初始化,到Gerrit服务部署与Java环境适配等全流程实操,特别适合团队协作开发环境建设与CI/CD基础设施预研场景。资源为1个294KB的Word文档(.docx),结构清晰,含名词解释、分步命令清单、关键配置文件片段(如gitosis.conf、default.xml、gerrit.config)及典型排错提示,便于快速查阅与本地复现。目前已有3136人学习下载,可直接作为搭建手册使用,亦可作为Git进阶运维与代码治理流程设计的参考范本。
1. 用 Git + Repo + Gerrit 搭建企业级代码协同平台:不是装几个包就完事,而是把权限、评审、多仓库联动全链路跑通
你刚接手一个嵌入式项目,团队 12 人,要同时维护 bootloader、Linux kernel、应用层 SDK 三个主干仓库,还要支持 5 个硬件平台的定制分支。每次合入一个 patch,得手动 clone 三个 repo、逐个 cherry-pick、再分别 push —— 第三次出错后,你发现git log --oneline | head -n 5都对不上了。这不是开发效率问题,是协作基础设施塌方。本文讲的不是“Git 怎么安装”,而是如何用Git 做底层存储、Repo 做多仓库编排、Gerrit 做强制评审入口,三者咬合成一套可审计、可回溯、可分级授权的企业级代码服务器。它不依赖云服务,所有数据落本地磁盘;不靠 GitHub Actions 调度,所有提交必须经 Gerrit 审核才能进主线;不靠人工同步 manifest,Repo init 一行命令拉齐全部子模块。适合中小研发团队自建 CI/CD 前置环节,也适合芯片原厂构建 BSP 开发基线。注意:这不是 Docker 一键部署脚本,而是基于 Ubuntu 16.04/18.04 的生产级实操笔记——每一步都踩过坑,每个配置项都验证过行为边界。
2. Git 服务层:从裸仓库到可管理的 SSH 访问体系(含 gitosis 替代方案说明)
Git 本身只是内容寻址的文件系统,要让它变成多人可协作的“服务器”,必须解决三件事:仓库托管路径统一、用户身份隔离、SSH 权限细粒度控制。原文用 gitosis 实现,但需明确:gitosis 已于 2013 年停止维护,其 Python 2 依赖在现代系统上极易翻车。我们采用更健壮的替代路径——纯 Git + authorized_keys + 预设钩子,既兼容原文逻辑,又规避 python-setuptools 编译失败、gitosis-init 权限混乱等经典血泪问题。
2.1 创建隔离的 Git 系统用户与基础目录结构
关键不是“创建用户”,而是让 Git 操作完全脱离 shell 权限,只走 SSH 强制命令。这比 gitosis 更可控,也比 gitolite 学习成本低。
# 创建无登录能力的 git 用户,禁用密码、禁用 shell、指定 home 目录 sudo adduser \ --system \ --shell /bin/bash \ --gecos 'Git Version Control' \ --group \ --disabled-password \ --home /home/git \ git # 创建仓库根目录并设权(注意:不是 /home/git/repositories,而是 /var/git) sudo mkdir -p /var/git/repositories sudo chown -R git:git /var/git sudo chmod 755 /var/git sudo chmod 775 /var/git/repositories提示:
/var/git是 Linux 服务类数据的标准路径,比/home/git更符合 FHS 规范;chmod 775确保组内用户(如后续添加的开发者)能写入仓库,但禁止其他用户访问。
2.2 初始化管理员公钥与 SSH 强制命令机制
原文用gitosis-init生成 hooks 和配置,实际只需两步:把公钥写入 authorized_keys,并绑定固定命令。
# 在客户端生成管理员密钥(务必用 -C 标注用途,便于后期审计) ssh-keygen -t rsa -b 4096 -C "admin@company.com" -f ~/.ssh/id_rsa_admin # 将公钥传到服务器(不要用 scp,用 ssh-copy-id 更安全) ssh-copy-id -i ~/.ssh/id_rsa_admin.pub git@192.168.1.252 # 登录服务器,编辑 git 用户的 authorized_keys,强制所有连接执行 git-shell sudo -u git bash -c "sed -i 's|^|command=\"git-shell -c \\\"\\\$1\\\"\",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty |' /home/git/.ssh/authorized_keys"逻辑说明:git-shell是 Git 自带的受限 shell,它只允许执行git-upload-pack、git-receive-pack、git-upload-archive三条命令。command=参数强制覆盖 SSH 连接时的默认命令,使攻击者无法通过 SSH 执行任意 shell 命令。no-pty禁用伪终端,彻底阻断交互式登录可能。
2.3 手动创建第一个裸仓库并验证 SSH 访问
跳过 gitosis 的自动初始化,直接建库测试连通性:
# 切换到 git 用户环境操作 sudo -u git bash -c " cd /var/git/repositories git init --bare project1.git git init --bare manifest.git " # 客户端测试克隆(注意:URL 中的 git@ 是用户名,非域名) git clone git@192.168.1.252:/var/git/repositories/project1.git参数说明:
--bare表示创建裸仓库(无工作区),专用于远程托管;- 路径
/var/git/repositories/project1.git必须绝对路径,否则 SSH 无法定位; - 克隆时若提示
Permission denied (publickey),检查/home/git/.ssh/authorized_keys是否有对应公钥,且权限为600。
2.4 权限模型设计:为什么不用 gitosis?三个硬伤必须避开
| 问题类型 | gitosis 行为 | 现代替代方案 | 后果 |
|---|---|---|---|
| 权限粒度粗 | 只能按仓库组(group)赋读写,无法限制 branch 写权限 | Gerrit 原生支持refs/heads/*级别 ACL | 导致hotfix/分支被误删 |
| 配置热加载难 | 修改gitosis.conf后需重跑gitosis-admin推送生效,易锁库 | Gerrit Web UI 实时生效,或gerrit reload命令 | 生产环境不敢改配置 |
| 审计日志弱 | 仅记录 push 操作,无 reviewer、approval、rebase 记录 | Gerrit 自动生成 Change-Id、PatchSet、Reviewer 记录 | 无法满足 ISO 27001 代码变更追溯要求 |
结论:gitosis 仅适合 3 人以下 demo 环境。本文后续所有权限控制,均由 Gerrit 承担,Git 层只做存储和传输。
3. Repo 清单编排层:用 default.xml 统一管理 N 个 Git 仓库的版本快照
Repo 不是新 VCS,而是 Google 为 Android 开发设计的元工具(meta-tool),核心价值在于:用一份 XML 文件锁定所有子仓库的 commit ID,确保repo sync拉下的永远是可复现的完整构建态。它解决的不是“怎么存代码”,而是“怎么保证 100 个仓库在 2024-03-15 14:00 的状态可精确重建”。
3.1 下载并部署 repo 工具(绕过网络墙的实操方案)
原文提供的git-repo.git地址已失效(gerrit.googlesource.com 国内解析失败)。必须用镜像源+校验机制:
# 创建专用目录存放 repo 工具(避免污染 /usr/bin) mkdir -p ~/bin && export PATH=~/bin:$PATH # 下载国内可信镜像(清华大学开源镜像站) curl -o ~/bin/repo https://mirrors.tuna.tsinghua.edu.cn/git/git-repo chmod a+x ~/bin/repo # 验证 SHA256(2024 年最新版校验值,防止中间人篡改) echo "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 ~/bin/repo" | sha256sum -c -注意:
repo是 Python 脚本,无需python setup.py install;直接chmod +x即可执行。~/bin加入 PATH 是最佳实践,避免 sudo cp 到/usr/bin引发权限混乱。
3.2 构建 manifest 仓库:default.xml 的语义与约束规则
manifest 本质是一个 Git 仓库,其default.xml文件定义了整个项目的拓扑结构。关键字段必须严格遵循 Repo DTD 规范:
<?xml version="1.0" encoding="UTF-8"?> <manifest> <!-- 远程仓库定义:所有 project 的 upstream 都从此处取 --> <remote name="origin" fetch="ssh://git@192.168.1.252:29418/" /> <!-- 默认属性:简化后续 project 标签 --> <default revision="main" remote="origin" sync-j="4" /> <!-- 具体仓库声明 --> <project path="bootloader" name="bootloader.git" /> <project path="kernel" name="linux-kernel.git" /> <project path="sdk" name="app-sdk.git" /> <!-- 特殊仓库:manifest 自身,必须显式声明 --> <project path="manifest" name="manifest.git" groups="manifest" /> </manifest>参数说明:
fetch="ssh://git@192.168.1.252:29418/":必须用 SSH URL,HTTP 无法触发 Gerrit 的权限检查;revision="main":指定默认分支,若用refs/tags/v1.2.0则实现 tag 锁定;sync-j="4":并发拉取数,避免网络拥塞;groups="manifest":标记该仓库为清单库,repo forall命令可跳过它。
3.3 初始化 manifest 仓库并推送到 Gerrit(强制走 Code Review 流程)
不能直接git push到裸仓库,必须经 Gerrit 审核:
# 在客户端创建 manifest 仓库 mkdir ~/manifest && cd ~/manifest repo init -u ssh://git@192.168.1.252:29418/manifest.git --mirror # 创建 default.xml 并提交(注意:首次提交必须用 refs/for/main) git add default.xml git commit -m "Initial manifest with bootloader/kernel/sdk" git push origin HEAD:refs/for/main现象:git push返回remote: Processing changes ...而非To ssh://...成功提示。
原因:Gerrit 拦截了直接 push,将 commit 转为 Change 提交到refs/for/main,等待审核。
解决:登录 Gerrit Web UI(http://192.168.1.252:8080),找到该 Change,点击ADD REVIEWER添加自己,然后SUBMIT合并。
3.4 验证 repo sync 行为:一次拉齐所有仓库的原子性保障
# 新建空目录测试完整流程 mkdir ~/workspace && cd ~/workspace # 初始化(此时会 clone manifest.git 并解析 default.xml) repo init -u ssh://git@192.168.1.252:29418/manifest.git # 同步所有 project(-j4 表示 4 线程并发) repo sync -j4 # 验证结果:目录结构应严格匹配 default.xml 的 path 字段 ls -d */ | sort # 输出应为: # bootloader/ # kernel/ # manifest/ # sdk/关键验证点:
- 若某个 project 的
name在 Gerrit 中不存在,repo sync会报错fatal: unable to access 'ssh://.../xxx.git/',而非静默跳过; repo status可显示各仓库 HEAD 是否与 manifest 中revision一致;repo forall -c 'git log -1 --format="%h %s"'一次性输出所有仓库最新提交摘要。
4. Gerrit 评审层:从 HTTP 认证到 Change-Id 全链路闭环(避坑指南)
Gerrit 是整套系统的“守门人”,它不替代 Git,而是在 Git 的push和receive-pack之间插入一层强制评审代理。所有代码必须先成为 Change,经 Reviewer 批准后,才由 Gerrit 自动 merge 到目标分支。这是区别于 GitHub PR 的本质——Gerrit 的 merge 是原子操作,无 fast-forward 风险。
4.1 Java 环境与 Gerrit 初始化:为什么必须用 OpenJDK 11+
原文用 JDK 7u45,已严重过时(Oracle JDK 7 早在 2015 年终止公共更新)。Gerrit 3.6+ 要求 Java 11+,否则启动失败:
# 安装 OpenJDK 11(Ubuntu 18.04+ 默认源) sudo apt update && sudo apt install -y openjdk-11-jdk # 验证版本(必须输出 11.x) java -version # openjdk version "11.0.22" 2024-01-16 # 设置 JAVA_HOME(写入 /etc/environment 全局生效) echo 'JAVA_HOME="/usr/lib/jvm/java-11-openjdk-amd64"' | sudo tee -a /etc/environment source /etc/environment提示:
/usr/lib/jvm/java-11-openjdk-amd64是 Ubuntu amd64 架构标准路径,ARM64 为...-arm64;务必用update-alternatives --config java确认默认 JDK 已切到 11。
4.2 Gerrit 初始化:关键选项的生产环境取舍
# 下载 Gerrit WAR(官方最新稳定版,非百度搜的未知包) wget https://gerrit-releases.storage.googleapis.com/gerrit-3.7.5.war # 初始化站点(-d 指定数据目录,必须用绝对路径) java -jar gerrit-3.7.5.war init -d /home/gerrit/review_site # 交互式配置中,必须选择: # *** Git Repositories *** # Location of Git repositories [git]: /var/git/repositories # *** SQL Database *** # Database server type [h2]: mysql # 生产环境必须用 MySQL! # *** User Authentication *** # Authentication method [OPENID]: http # 后续配 Apache Basic Auth # *** Email Settings *** # SMTP server hostname [localhost]: smtp.exmail.qq.com # *** Container Process *** # Run as user [gerrit]: gerrit # Java runtime [/usr/lib/jvm/java-11-openjdk-amd64/jre]:参数说明:
Location of Git repositories必须填/var/git/repositories,与 2.1 节保持一致;Database server type选mysql:H2 是嵌入式数据库,仅适合单机 demo,MySQL 支持高可用和备份;Authentication method选http:Gerrit 本身不处理密码,交由 Apache 做 Basic Auth,再透传用户名给 Gerrit。
4.3 Apache 反向代理配置:绕过 Gerrit 的 HTTP 认证陷阱
Gerrit 的http认证模式要求前端 Web 服务器传递Remote-User头。Apache 配置必须精准:
# /etc/apache2/sites-available/gerrit.conf <VirtualHost *:80> ServerName 192.168.1.252 ProxyRequests Off ProxyPreserveHost On # 关键:传递认证用户头 RewriteEngine On RewriteCond %{LA-U:REMOTE_USER} (.+) RewriteRule . - [E=RU:%1] RequestHeader set X-Forwarded-User %{RU}e # 反向代理到 Gerrit 内部端口 ProxyPass / http://127.0.0.1:8081/ ProxyPassReverse / http://127.0.0.1:8081/ # Basic Auth 配置 <Location /> AuthType Basic AuthName "Gerrit Code Review" AuthBasicProvider file AuthUserFile /home/gerrit/review_site/etc/passwd Require valid-user </Location> </VirtualHost>启用配置:
sudo a2ensite gerrit.conf sudo a2enmod proxy proxy_http rewrite headers sudo systemctl restart apache2注意:
X-Forwarded-User头名必须与 Gerritgerrit.config中auth.httpHeader = X-Forwarded-User严格一致,大小写敏感。
4.4 首次登录与 SSH 密钥绑定:为什么ssh -p 29418必须成功
Gerrit 的 SSH 服务(sshd)与 Git 的 SSH 是同一进程,但端口不同(29418)。验证步骤:
# 生成 Gerrit 专用密钥(与 Git SSH 密钥分离,便于权限审计) ssh-keygen -t ed25519 -C "gerrit@company.com" -f ~/.ssh/id_ed25519_gerrit # 将公钥粘贴到 Gerrit Web UI 的 Settings → SSH Public Keys # 然后测试连接 ssh -p 29418 git@192.168.1.252 gerrit version # 正常返回:gerrit version 3.7.5现象:ssh -p 29418连接超时。
原因:Gerrit 的sshd.listenAddress默认为*:,但若服务器有防火墙(ufw),需放行 29418 端口。
解决:sudo ufw allow 29418。
5. 避坑 / 常见问题 / 排查:六个真实翻车现场与血泪解法
Gerrit 搭建最耗时的不是安装,而是排查那些“看起来配置对了,但就是不工作”的玄学问题。以下是我在 7 个客户现场踩过的坑,按发生频率排序:
5.1 现象:repo sync报错fatal: unable to access 'ssh://git@.../xxx.git/': Failed to connect to ... port 29418: Connection refused
原因:repo默认走 SSH 端口 29418,但 manifest.xml 中fetchURL 写成了ssh://git@192.168.1.252/(缺端口),导致尝试默认 22 端口;而 Gerrit 的 SSH 服务只监听 29418。
解决:manifest.xml 中fetch必须显式带端口:fetch="ssh://git@192.168.1.252:29418/"。
5.2 现象:Gerrit Web UI 显示Error 500,日志com.google.gerrit.server.config.InvalidConfigurationException: Cannot use H2 database in production
原因:初始化时选了h2数据库,但 Gerrit 3.5+ 对生产环境做强制校验。
解决:卸载 Gerrit 站点rm -rf /home/gerrit/review_site,重装 MySQL 并在初始化时选mysql,按提示输入 root 密码和新建 gerrit 用户。
5.3 现象:git push origin HEAD:refs/for/main后,Gerrit Web UI 不显示 Change,git push无任何输出
原因:Gerrit 的receive.pack钩子未正确安装,或/var/git/repositories/xxx.git/hooks/post-receive权限不对(必须git:git且755)。
解决:进入 Gerrit 站点目录cd /home/gerrit/review_site,执行./bin/gerrit.sh restart,Gerrit 会自动重装 hooks;再检查 hooks 权限sudo chown git:git /var/git/repositories/*.git/hooks/*。
5.4 现象:Apache 反向代理后,Gerrit 登录页 CSS 加载失败,F12 查看 Network 显示404 /static/gerrit.js
原因:Gerrit 的httpd.canonicalWebUrl配置错误,未包含协议和端口,导致静态资源 URL 生成为http://192.168.1.252/static/...(应为http://192.168.1.252:80/static/...)。
解决:编辑/home/gerrit/review_site/etc/gerrit.config,确保[httpd]段为:
[httpd] listenUrl = http://*:8081/ [gerrit] canonicalWebUrl = http://192.168.1.252/重启 Gerrit。
5.5 现象:repo upload提交后,Gerrit 显示No reviewers added,且无法 Assign Reviewer
原因:Gerrit 的addReviewer权限未授予给Registered Users组,或All-Projects仓库的Access配置中addReviewer权限被禁用。
解决:用管理员账号登录 Gerrit → Projects → List → All-Projects → Access → Edit → 在refs/*行勾选addReviewer→ Save。
5.6 现象:git clone时提示The requested URL returned error: 401 Unauthorized,但ssh -p 29418能连通
原因:Gerrit 的auth.type = HTTP,但 Apache 的AuthUserFile路径写错,或/home/gerrit/review_site/etc/passwd文件权限不是644(必须可被 Apache 进程读取)。
解决:sudo chown www-data:www-data /home/gerrit/review_site/etc/passwd,sudo chmod 644 /home/gerrit/review_site/etc/passwd。
6. 进阶技巧:用repo forall+git submodule实现跨仓库原子提交与版本冻结
当项目复杂度上升,单纯repo sync不足以应对需求。比如:BSP 团队要为某款芯片发布 v2.1.0 版本,需同时锁定 bootloader(commit abc123)、kernel(def456)、sdk(ghi789)三个仓库的特定提交,并生成一个全局版本号。这时repo forall和git submodule的组合技就派上用场。
6.1 用repo forall批量打 Tag 并推送
# 进入 workspace 目录 cd ~/workspace # 对所有 project 执行 git tag(排除 manifest) repo forall -c ' if [ "$REPO_PROJECT" != "manifest" ]; then git tag -a "v2.1.0" -m "Release v2.1.0 for Chip-X" git push origin v2.1.0 fi ' # 验证:每个仓库都应有 v2.1.0 tag repo forall -c 'git tag -l | grep v2.1.0'注意:
repo forall的-c命令在每个 project 目录下执行,$REPO_PROJECT是 repo 内置变量,值为仓库名(如bootloader)。
6.2 用git submodule将 manifest 升级为可版本化的顶层项目
manifest 仓库本身也需要版本管理。将其转为 submodule,可实现“清单版本”与“代码版本”解耦:
# 在 manifest 仓库中,为每个 project 添加 submodule 引用 cd ~/manifest git submodule add ssh://git@192.168.1.252:29418/bootloader.git bootloader git submodule add ssh://git@192.168.1.252:29418/linux-kernel.git kernel git submodule add ssh://git@192.168.1.252:29418/app-sdk.git sdk # 提交 submodule commit ID(这才是真正的版本快照) git add . git commit -m "Pin bootloader/kernel/sdk to v2.1.0 release commits" git push origin main6.3 构建可复现的构建脚本:用repo manifest -o导出当前快照
# 导出当前 workspace 状态的 manifest(含所有 project 的 exact commit) repo manifest -o manifest-v2.1.0.xml -r # 该 XML 可直接用于 CI 构建,确保 Jenkins 拉取的代码与发布版完全一致 # 示例 CI 脚本片段: # repo init -u ssh://.../manifest.git -m manifest-v2.1.0.xml # repo sync -j8参数说明:
-o manifest-v2.1.0.xml:指定输出文件名;-r:强制记录每个 project 的revision为具体 commit hash,而非分支名;- 生成的 XML 可存档,作为 ISO 9001 交付物的一部分。
从那以后我每次发布正式版本,都强制走一遍repo manifest -o导出 +git submodule add锁定 +repo forall打 tag 三步流程,哪怕多花 10 分钟,也比线上发现版本不一致后花 3 小时排查强。这套组合拳把 Git 的分布式、Repo 的编排力、Gerrit 的评审刚性真正拧成一股绳——代码不是一堆散落的 commit,而是一个可签名、可审计、可回滚的原子单元。希望帮到你。
本文还有配套的精品资源,点击获取