在 GitLab 上开一个自己的仓库,这件事听起来像是"点几下鼠标"的功夫,真动手做的时候,坑往往藏在第二步和第三步:SSH 密钥配了半天还是 Permission denied,克隆下来的地址是一串机器 ID 而不是域名,README 一勾选本地推送直接冲突,想搬个旧项目过来结果提交历史全丢了。我自己第一次建仓库是在一台内网服务器上,折腾了整整一个下午,最后发现只是 external_url 那一行没改。所以这篇东西不打算从"什么是版本控制"讲起,而是把建仓库这条链路从头到尾拆开:账号准备、密钥配置、网页端建仓、本地代码入库、分支与合并、CI/CD 联动、以及那些只在出事时才会去查的报错。适合刚接触 GitLab 的开发者,也适合需要给团队搭一套内部代码仓库的运维同学,看完应该能直接照着做一遍。
1. 建仓库之前,先把这几件事想清楚
很多人建仓库的习惯是"先建了再说",结果三个月后回头看,仓库列表里躺着一堆 test、demo、new-project 这种名字,分支乱成一团,谁也说不清哪个是能跑的最新版。仓库这个东西,建的时候只花两分钟,维护的时候要花两年,前期多想十分钟是划算的。
1.1 仓库到底装什么,决定了它长什么样
先给仓库做个分类,因为不同用途的仓库,配置策略完全不一样。
第一类是个人练手仓库,特点是一个人写、写完就放着、偶尔回来改。这种仓库不需要保护分支,不需要合并请求,甚至可以不要 CI,建 Private 就行,随手推随手改,怎么舒服怎么来。第二类是团队协作仓库,一个主分支加若干功能分支,必须开保护分支、必须走合并请求、必须有代码评审,否则早晚会出现有人直推 main 把别人代码覆盖掉的事故。第三类是配置与脚本仓库,比如部署脚本、Nginx 配置、Ansible playbook,这类仓库的特点是内容碎、改动频繁、往往涉及一些环境信息,可见性一定要设成 Private,敏感配置该走变量就走变量,不要图省事硬编码进文件。
还有一类容易被忽略的,是归档型仓库。项目已经下线了,但代码还想留着做参考。这种仓库建完之后建议直接 Archive 掉,只读不写,免得以后误操作。
分类的意义在于:你建仓库那一刻勾选的选项,其实就是给未来的自己定规矩。
1.2 自建服务还是用托管平台,三条路线的取舍
GitLab 有三种用法,选错了会很别扭。
| 路线 | 适合场景 | 优点 | 需要注意的点 |
|---|---|---|---|
| 官方托管服务 | 个人、小团队、开源项目 | 开通即用,无需维护服务器 | 免费额度有配额限制,网络访问质量受链路影响 |
| 自建服务器(Docker 或安装包) | 中大型团队、内网环境 | 数据自控,可对接内部系统 | 需要专人维护、备份、升级,升级前必须看版本兼容 |
| 局域网内单机部署 | 实验室、封闭网络、教学环境 | 不依赖外网,速度快 | 需自行处理域名解析与证书,外部访问要额外规划 |
自建这条路,最常见的是用 Docker 起一个单机实例,或者用官方提供的 Linux 安装包(Omnibus)离线部署。Docker 方式的好处是环境干净、迁移方便;安装包方式的好处是升级路径清晰、官方文档覆盖全。如果是完全离线的内网环境,通常是先在能联网的机器上把安装包和依赖拉全,再整体搬到目标机器上装,这里最容易被忽略的是依赖包的完整性和系统时间同步,时间不同步会导致令牌校验失败,报出来的错往往跟真实原因八竿子打不着。
局域网部署还有一个专属问题:同事之间怎么访问。如果只有 IP 没有域名,克隆地址就会变成一串 192.168.x.x,换网络就废了。这个问题后面第 2.4 节会专门讲。
1.3 仓库命名和可见性,别等出事才后悔
命名规范这件事,团队里如果没有约定,半年后一定会乱。我推荐的最小规范是:全小写、单词之间用中划线、不用中文、不用拼音缩写。比如order-service、user-center-api、ops-deploy-scripts。这么做的好处是命令行里不用来回切输入法,URL 也干净,脚本里拼接路径不会因为大小写出问题。
可见性三档,含义差别很大:
- Private:只有被显式加进来的人能看,个人项目默认选这个。
- Internal:所有登录该实例的用户都能看,自建环境下这个选项很危险,等于全员可见。
- Public:任何人(包括未登录)都能看,开源项目才用。
很多人第一次建仓时随手选了 Internal,觉得"反正我们人少",结果实习生都能翻到你的部署脚本。默认选 Private,需要共享时再逐个加人,这是最省心的做法。
命名空间也值得说一句。GitLab 里每个仓库归属一个命名空间,可以是你的个人账号,也可以是一个 Group。个人账号下的仓库,人一离职就麻烦了,转移起来还要走流程。团队项目从一开始就建在 Group 下面,权限跟着组走,人员进出只需调整组成员,仓库路径也不用变。
2. 从零开始:账号、SSH 与本地环境的准备
环境准备这一步,属于"做一次管很久"的投入。SSH 密钥配好之后,之后几年推代码都不用输密码,也是最值得花时间的地方。
2.1 注册、首次登录与初始安全设置
托管平台的注册流程比较直白:填邮箱、设密码、收验证邮件、点确认。真正的坑在首次登录之后的三件事。
第一是管理员是否开放了自助注册。自建环境下,很多团队会把注册关掉,改成管理员手动创建账号并分配初始密码。第一次登录会被强制要求改密码,这一步不能跳。第二是两步验证,团队仓库强烈建议开启,用任意一款 TOTP 应用扫码即可,开启后记得把恢复码存到密码管理器里,否则换手机那天会很难受。第三是个人访问令牌的认知——现在很多平台已经不允许用账号密码做 Git 操作了,只能走 SSH 密钥或访问令牌,这就是后面那些认证类报错的根源。
如果你是自己搭的实例,还有一个隐藏项:默认分支名。新版本默认是 main,老版本默认是 master,这个设置决定了你网页端建仓时初始化的分支叫什么。跟本地习惯不一致的话,第一次推送就会多一步改名。
2.2 SSH 密钥:一次配置,长期免密
我建议直接用 ed25519,比 RSA 更短更快,兼容性也早就不是问题了。
ssh-keygen -t ed25519 -C "你的邮箱@example.com" -f ~/.ssh/id_ed25519_gitlab这里的-f是关键。如果你同时用多个代码平台,一定要给不同平台的密钥起不同文件名,否则会互相覆盖,出现"昨天还能推,今天就不行了"的诡异现象。生成过程中会问你要不要设 passphrase,设了更安全,但每次用都要输;可以配合系统的密钥管理工具做免输,看你自己的取舍。
生成后拿到公钥:
cat ~/.ssh/id_ed25519_gitlab.pub把这一整行(以ssh-ed25519开头)复制到 GitLab 的Preferences → SSH Keys页面。注意几个细节:过期时间建议留空或者设一个足够远的日期,否则几个月后突然失效;标题写清楚是哪台机器的,以后要删的时候不至于认不出来。
然后写一个本地配置,让 Git 知道访问这个域名时用哪把钥匙:
# ~/.ssh/config Host gitlab.example.com HostName gitlab.example.com User git IdentityFile ~/.ssh/id_ed25519_gitlab IdentitiesOnly yesIdentitiesOnly yes这行很重要,它避免 SSH 到处试别的密钥,也避免某些服务器因为尝试次数过多直接拒连。
验证是否通了,用这条:
ssh -T git@gitlab.example.com自建环境的 SSH 端口如果不是 22,克隆地址里会带上端口号,那本地 config 里也要同步加上Port一行。端口不匹配是"密钥明明贴对了还是连不上"的头号原因。
2.3 本地 Git 身份配置,别用全局配置敷衍
Git 提交要记录作者信息,这个信息来自user.name和user.email。很多人一上来就git config --global设一遍,然后公司和个人的提交记录全混在一起。
我的做法是:全局只设一个兜底身份,具体项目用includeIf按目录切换。
git config --global user.name "个人昵称" git config --global user.email "me@personal.com" git config --global includeIf."gitdir:~/work/".path ~/.gitconfig-work然后在~/.gitconfig-work里写工作身份。这样放在~/work/目录下的仓库,自动用工作邮箱提交,其它目录用个人身份。这个配置我推荐所有人做一遍,尤其是在同一台电脑上既写公司代码又写个人项目的场景。
还有一个细节:邮箱要和 GitLab 账号里的邮箱一致,否则提交记录不会关联到你的头像和贡献图,同事点进去看到的是一个没有头像的陌生名字。这个不是功能问题,但排查起来很费时间,因为一切看起来都正常。
2.4 克隆地址是机器 ID 而不是域名,怎么改成域名
这个问题在自建环境里出现频率极高,值得单独讲一段。
现象是:网页上给出的克隆地址长这样http://192.168.1.100/group/repo.git,或者git@192.168.1.100:group/repo.git,你想把它换成gitlab.example.com,但不知道从哪改。
根源在于 GitLab 的克隆地址完全由配置项external_url决定。要改它,编辑配置文件:
sudo vi /etc/gitlab/gitlab.rb找到并修改:
external_url 'http://gitlab.example.com'如果服务映射在非标准端口上,端口要写进这个 URL 里,比如http://gitlab.example.com:8929。改完之后执行:
sudo gitlab-ctl reconfigure等待配置跑完,再刷新页面,克隆地址就会变成域名。同时别忘了在gitlab-ctl reconfigure之前把 DNS 或者本地 hosts 配好,让gitlab.example.com能解析到服务器地址,否则改完之后你自己都访问不了。
这里有两个实操心得。一是改 external_url 不会自动修改已存在仓库的 remote 地址,如果同事的本地仓库早就 clone 过了,他们的origin还是老的 IP,需要手动改:
git remote set-url origin git@gitlab.example.com:group/repo.git二是如果之前用的是 HTTPS,现在想切 SSH,同样是这一条命令,把 URL 换成 SSH 形式即可,git remote -v可以随时查看当前配置。
补充一点,如果你的实例前面挂了反向代理,external_url要写外部访问的地址,反向代理那边要把请求头正确透传,否则会出现重定向循环或者静态资源 404 的情况。
3. 手把手建仓库:界面操作与本地初始化
前面都是铺垫,这一节是真正的"建仓库"动作。网页端点几下就能建出来,但要建得不出后续麻烦,几个复选框一定要看清楚。
3.1 网页端创建空仓库,四个关键选项
从首页右上角 New project → Create blank project 进入,需要填和选的东西就四样。
Project name:填人类可读的名字,可以带空格和大写。下面的 Project slug 是自动生成的 URL 片段,会自动转成小写和中划线,这个可以手动改,但建议跟 name 保持一致,别一个叫"订单服务"一个 slug 是test123。
Project URL:选择命名空间。个人项目选自己的用户名,团队项目选对应的 Group。这一步选错了,后面要转移会比较麻烦。
Visibility:按第 1.3 节的建议,默认 Private。
Initialize repository with a README:这是最容易出事的一个勾选框。如果你本地已经有一堆代码要推上去,不要勾。勾了之后远程仓库会有一个初始提交(README 文件),而你本地也有自己的提交,两边历史无关,推送时会被拒绝,报updates were rejected because the remote contains work that you do not have locally。新手看到这个错误通常会慌张地去git push -f,如果这时候有别人的代码在上面,后果就比较严重了。
正确做法是:本地已有代码就不勾 README,建出一个完全空的仓库,然后按 3.3 的流程推上去。如果你确实想要 README 和 .gitignore 模板,可以选择先git pull --rebase origin main把远程的初始提交拉下来合并,再推,这样历史是干净的。
建好之后,GitLab 会在页面上直接给你一段命令提示,告诉你接下来怎么把本地代码推上来,这段提示其实已经够用了,下面我把它补全。
3.2 三种把代码放进仓库的方式,怎么选
| 方式 | 适用场景 | 优点 | 局限 |
|---|---|---|---|
| 命令行推送 | 本地已有完整项目 | 历史完整、可控 | 需要本地装 Git 并配好密钥 |
| 网页上传 / Web IDE | 临时改一两个文件、没有本地环境 | 零安装、快 | 不适合大项目,无法批量处理 |
| 导入已有仓库 | 从其它平台或旧服务搬家 | 保留提交历史与分支 | 大仓库容易超时,需要看导入限制 |
日常开发用命令行,临时补个配置文件用网页上传,搬家场景用导入功能。三种方式不冲突,一个仓库可以都用到。
3.3 命令行完整流程,含首次推送
假设本地已经有一个项目目录,里面是要入库的代码。完整流程如下:
cd your-project # 1. 初始化本地仓库 git init # 2. 看一下当前状态,确认哪些文件会被纳入 git status # 3. 编写 .gitignore,把不该进仓库的东西排除掉 # 目标产物、依赖目录、本地配置、密钥文件 cat > .gitignore <<'EOF' node_modules/ dist/ target/ .idea/ .vscode/ *.log .env *.pem EOF # 4. 暂存并提交 git add . git commit -m "chore: 初始化仓库" # 5. 确认分支名,跟远程保持一致 git branch -M main # 6. 关联远程 git remote add origin git@gitlab.example.com:your-group/your-project.git # 7. 首次推送并建立追踪关系 git push -u origin main第 3 步的.gitignore是整个流程里我最想强调的地方。密钥文件、.env、日志、编译产物,这些一旦推上去,即使后面删掉,历史记录里依然查得到。推上去之前先看一眼git status的输出,比事后清理省事一百倍。
第 6 步有个新手常见的错误:把origin拼错或者把 URL 里的冒号写成斜杠。SSH 形式的地址是git@host:group/repo.git,中间是冒号不是斜杠,HTTPS 形式才是https://host/group/repo.git。写错了会报does not appear to be a git repository。
推送功能分支:
git checkout -b feature/order-cache # 改代码... git add . git commit -m "feat: 增加订单缓存" git push -u origin feature/order-cache首次推送功能分支也要带-u,这样以后直接git push就行。推完之后在网页上会看到一个提示条,可以直接点它创建合并请求,这是 GitLab 体验比较顺的一点。
如果推送时报failed to push some refs,先别急着加-f。先执行git fetch origin看看远程有什么,再决定是合并还是变基。
3.4 从别的地方搬过来:导入与迁移
已经有代码在别的平台或者旧服务上,不想一条条手动推,可以用导入功能。入口是 New project → Import project,支持从常见的代码托管平台直接导入,也支持通过仓库 URL 导入,还有从 manifest 文件批量导入的方式。
几种情况的处理思路不一样。同类型平台之间迁移,一般直接授权就能拉,提交历史、分支、标签都能保留,最省事。从旧的自建服务迁移(比如一些轻量的 Git 服务),通常走"按 URL 导入",需要提供旧仓库的地址和一个有读取权限的凭证。跨网络或者旧服务已经关停,那就只能先在本地 clone 一份裸仓库,再推送到新地址:
git clone --mirror git@old-host:group/repo.git cd repo.git git remote set-url origin git@gitlab.example.com:group/repo.git git push --mirror--mirror会把所有分支、标签、引用全带过去,这是保留完整历史最稳的方式。推完之后本地这个裸仓库就可以删了。
导入环节最容易踩的坑有三个:一是大仓库超时,几百兆以上的仓库经常导到一半断掉,稳妥做法是本地 mirror 再推;二是LFS 对象丢失,如果原仓库用了 Git LFS,要先把 LFS 对象拉全再迁移,否则迁过去发现大文件是个指针;三是迁移后远程地址没更新,同事还在往旧地址推,过了几天才发现新仓库少了一周的提交。
4. 仓库建好之后的日常:分支、合并与协作规范
仓库建出来只是起点,真正决定它好不好用的,是后面几个月的使用习惯。这一节讲的是那些"不设也能跑,设了能省很多事"的配置。
4.1 一套够用的分支模型
团队规模在十人以内的话,不需要上来就搞复杂的分支策略,三个层级就够:
- main:随时可发布的状态,只读保护,只能通过合并请求进入。
- develop:集成分支,功能开发完成后合到这里做联调。
- feature/xxx:功能分支,一个人一个,命名带上需求编号或短描述。
如果是持续发布的小团队,甚至可以省掉 develop,功能分支直接往 main 合,前提是 CI 必须跑过。分支越多,合并冲突的概率越高,不是越复杂越好。
分支命名我建议加前缀区分类型:feature/、fix/、hotfix/、chore/。这样在分支列表里一眼能看出哪些可以删、哪些还活着。长期不动的分支要定期清理,否则列表会变成考古现场。
还有一个很实用的操作:从 Issue 直接创建分支。GitLab 在 issue 详情页有一个"创建合并请求和分支"的入口,用这种方式创建的分支,名字会自动带上 issue 编号,合并时也会自动关联并关闭 issue,省掉手工写关联关键词的麻烦。
4.2 保护分支与合并请求,把事故挡在门外
保护分支的入口在 Settings → Repository → Protected branches。至少要保护 main,配置项有三个:谁能合并、谁能推送、是否允许强制推送。
推荐配置是:Merge 权限给 Maintainer,Push 权限设为 No one,强制推送关闭。这样任何改动都必须通过合并请求,直接git push到 main 会被服务器拒绝,这是防止误操作最有效的一道闸。
合并请求本身也值得配置几项。第一是合并前必须通过流水线,也就是设置里勾上 Pipelines must succeed,代码编译都不过就别合了。第二是合并后删除源分支,这个选项默认开启,能把分支列表保持得比较干净。第三是合并方式,团队里选一种并统一:Merge commit 保留完整历史,Squash 把多个提交压成一个,Rebase 保持线性历史。我个人偏向 Squash,功能分支上的十几个"fix typo"提交压成一条,主干历史会清爽很多。
合并冲突是绕不开的。冲突时 GitLab 网页端会显示哪几个文件冲突,小冲突可以直接在网页上解决,大冲突还是建议本地处理:
git checkout feature/order-cache git fetch origin git rebase origin/main # 解决冲突,git add 标记已解决 git rebase --continue git push --force-with-lease origin feature/order-cache注意是--force-with-lease而不是--force。前者在远程有你不知道的新提交时会拒绝推送,相当于给你留了一次反悔的机会。这是我踩过坑之后改掉的习惯,强烈建议你也改。
4.3 标签与版本发布
代码要发版的时候,用标签比用分支靠谱。分支会移动,标签不会。
git tag -a v1.2.0 -m "发布 1.2.0:新增订单缓存与批量导出" git push origin v1.2.0推完标签之后,可以在 GitLab 的 Releases 页面基于这个标签创建一个发布记录,写上更新说明,附上构建产物。这样以后回溯"线上跑的是哪个版本、那个版本改了什么",点两下就能查到。
标签命名建议严格遵循语义化版本:主版本号.次版本号.修订号。破坏性改动升主版本,新增功能升次版本,修 bug 升修订号。别用日期当标签名,20240513这种看不出兼容性。
4.4 归档、清理与删除,该收尾时就收尾
不用的仓库怎么处理,分两种情况。
项目彻底结束但想留档,用Archive(Settings → General → Advanced → Archive project)。归档后仓库变成只读,不再出现在活跃列表里,也不会占用额外的流水线配额,但代码随时能翻出来。这是最推荐的"善终"方式。
确实要彻底删除,也是同一个页面的 Delete project,需要输入仓库全路径确认。这里有个必须知道的点:删除不是立刻物理清除,通常有一段保留期,期间管理员还有机会恢复。所以手滑删错了不要慌,第一时间找管理员。但也不要因此就觉得删除无风险,保留期一过就是真的没了。
还有一种情况是仓库没删但变"胖"了,clone 一次要等很久。原因通常是历史里混进了大文件,比如误提交的安装包、数据集、视频。这种问题删文件是没用的,因为历史里还在。需要用专门的历史重写工具把大文件从所有提交里剔除,然后强制推送到远程。这个操作会影响所有协作者,做之前一定要通知到位,让每个人重新 clone,否则他们会把旧历史又推回去。
5. 进阶:让仓库自己跑起来
仓库能存代码只是及格线,真正拉开效率差距的是自动化。这一节讲怎么把 CI/CD、镜像仓库、依赖加速这几块拼起来。
5.1 一份最小可用的流水线配置
在仓库根目录放一个.gitlab-ci.yml,就自动开启了流水线。最小可用的模板长这样:
stages: - build - test - deploy variables: MAVEN_OPTS: "-Dmaven.repo.local=.m2/repository" build-job: stage: build image: maven:3.9-eclipse-temurin-17 script: - mvn -B clean package -DskipTests artifacts: paths: - target/*.jar expire_in: 7 days test-job: stage: test image: maven:3.9-eclipse-temurin-17 script: - mvn -B test rules: - if: '$CI_PIPELINE_SOURCE == "merge_request_event"' deploy-job: stage: deploy script: - echo "执行部署脚本" rules: - if: '$CI_COMMIT_BRANCH == "main"'几个关键点解释一下。stages定义阶段顺序,同阶段的任务并行跑。artifacts把构建产物传给后续阶段,expire_in控制保留时间,不设的话产物会一直堆在服务器上,磁盘迟早会满——这是我被运维找过最多的一次。rules控制任务什么时候执行,比老式的only/except更直观,也是现在推荐的写法。
variables里那行 Maven 本地仓库路径,是为了让依赖缓存到项目目录,方便配合cache关键字做缓存,第二次跑流水线能省掉大量下载时间。
5.2 Runner 注册与连接不上怎么排查
流水线要有 Runner 才能跑。自建 Runner 的注册流程是:在项目或群组的 Settings → CI/CD → Runners 里拿到注册令牌,然后在装了 GitLab Runner 的机器上执行注册命令,选择执行器(Docker 执行器最常用,环境隔离好)。
gitlab-runner register \ --url http://gitlab.example.com/ \ --registration-token <你的令牌> \ --executor docker \ --docker-image alpine:latest \ --description "docker-runner-01" \ --tag-list "docker,linux"注册完常见的问题有几类。任务一直 pending 不跑,通常是标签不匹配,流水线里写了tags: [build]但 Runner 只打了docker标签,最简单的方法是先不加 tags,确认能跑起来再细化。克隆代码失败,多半是 Runner 容器里访问不到 GitLab 地址,这时候要把external_url对应的域名在 Runner 容器里也能解析,或者在配置里指定 clone_url。权限报错,检查 Runner 用的令牌有没有对应项目的权限。
还有一个容易忽视的点:如果 CI 里需要调用 GitLab 的 API(比如自动创建 issue、上传产物、触发下游流水线),需要一个访问令牌。令牌权限给太大有风险,给太小会报权限错误;同时如果实例和令牌所属版本差异较大,接口行为可能不一致,会看到类似"登录失败,请检查 API 令牌或版本"这样的提示。遇到这类报错,先核对三件事:令牌是否过期、令牌的作用域是否覆盖要调用的接口、实例版本是否在支持范围内。基本能定位到九成的问题。
5.3 构建产物与镜像,往哪放
CI 跑出来的东西需要一个地方存。常见有两类:通用制品仓库(存 jar、zip、tar 这类文件)和容器镜像仓库(存 Docker 镜像)。
通用制品可以挂在 GitLab 自己的 Package Registry 上,也可以对接独立的制品服务。有些团队会用通用的 Nexus 实例来做统一管理,通过不同的仓库类型分别存 Maven 包、npm 包、原始文件。这里有个容易踩的坑:用反向代理转发到某个特定路径的仓库时,路径重写规则如果写错了,请求会落到默认仓库上,表现为"上传成功但下载 404"。排查方法是直接看 Nexus 的请求日志,确认请求实际落到了哪个仓库,比对着反代配置猜要快得多。
镜像仓库方面,自建环境常用 Harbor 做私有镜像仓库。CI 里的典型流程是:构建镜像、按提交号打标签、推送到 Harbor、在部署阶段拉取。要注意的是镜像标签策略,只用 latest 会导致回滚时不知道回滚到哪个版本,建议至少同时打两个标签:一个语义化版本,一个提交短 SHA。
build-image: stage: build image: docker:24 services: - docker:24-dind script: - docker build -t harbor.example.com/group/app:$CI_COMMIT_SHORT_SHA . - docker push harbor.example.com/group/app:$CI_COMMIT_SHORT_SHA用 Docker 执行器跑镜像构建时,通常需要挂载宿主机的 Docker 套接字或者起一个 dind 服务,两种方式各有取舍:挂载套接字性能好但隔离性差,dind 隔离好但每次构建要多一层开销。团队内网环境用挂载套接字更常见。
5.4 依赖下载慢,怎么配镜像仓库
CI 慢很多时候不是代码慢,是依赖下载慢。以 Maven 为例,默认从中央仓库拉,网络条件一般的话构建时间会被拉得很长。解决办法是在settings.xml里配置镜像:
<mirrors> <mirror> <id>aliyun-mirror</id> <mirrorOf>central</mirrorOf> <name>国内镜像</name> <url>https://maven.aliyun.com/repository/public</url> </mirror> </mirrors>这里要区分清楚两个概念,很多人会混:mirror是拦截请求,把对某个仓库的访问重定向到另一个地址,mirrorOf写central就是接管中央仓库,写*就是接管全部;repository是新增一个可用的仓库地址,在不覆盖原有仓库的前提下补充来源。你想加速就用 mirror,你想引入一个私有仓库就用 repository,两者用途不一样。
如果团队内部有私有制品仓库,通常还要在settings.xml里加一段server配置,写上私有仓库的账号和凭据,再用 profile 在特定项目里激活。凭据不要明文写在文件里,CI 环境应该用流水线变量注入,本地开发则用系统密钥管理工具。
npm、pip 这些生态同理,都是换一个速度更快的源。如果遇到某个包管理器报访问失败,先确认源的地址是否可达,再确认这个源里是否有你要的包,最后再看认证配置,按这个顺序排查比乱试要快。
6. 常见问题与排查技巧实录
这一节是我自己攒的问题清单,基本覆盖了建仓和用仓过程中会遇到的高频故障。遇到问题时按现象对号入座,比漫无目的地搜要高效。
6.1 认证与权限类问题速查
| 现象 | 常见原因 | 处理方式 |
|---|---|---|
| Permission denied (publickey) | 未上传公钥、密钥文件不匹配、SSH 端口错 | 用ssh -T -v查看实际用了哪把钥匙;检查 config 里的 Host 和 IdentityFile |
| 提示需要用户名密码但输不对 | 平台已禁用密码认证 | 改用 SSH 或创建访问令牌,用令牌当密码 |
| 能克隆不能推送 | 该分支是保护分支,或角色权限不足 | 走合并请求;联系维护者调整角色 |
| 同事能推我不能推 | 你的账号未被加入项目或组 | 检查成员列表和继承的组权限 |
| 令牌突然失效 | 令牌过期或被轮换 | 重新生成并更新到本地或流水线变量里 |
排查权限问题的通用思路是"先确认身份,再确认权限"。身份用ssh -T或者打印当前 remote 地址确认;权限去项目的 Members 页面看自己的角色,再去看目标分支是不是受保护的。这两步走完,八成问题就有答案了。
6.2 推送、克隆失败类问题
remote contains work that you do not have locally:远程有你本地没有的提交,通常是建仓时勾了初始化 README。先git pull --rebase origin main把远程内容合进来,再推。千万别条件反射地强推。
推送超时或者中途断开:仓库太大、单次提交包含的文件太多,或者网络链路抖动。可以先增大 Git 的 HTTP 缓冲区:
git config --global http.postBuffer 524288000如果还是不行,改用 SSH 协议往往更顺。另外,历史里混了大文件的话,缓冲区调再大也没用,得走历史重写。
中文文件名显示乱码:设置一下编码就好。
git config --global core.quotepath false git config --global i18n.commitEncoding utf-8克隆下来是空目录:多半是克隆错了分支,或者克隆的是子模块目录。先git branch -a看看有哪些分支。
提示文件超过大小限制:平台一般对单个文件有上限,二进制大文件应该走 Git LFS。接入 LFS 之后,之前提交的历史不会自动转过去,需要额外做一次迁移。
6.3 自建部署与运行状态类问题
自建实例的问题跟托管平台完全不是一回事,这一块单独列出来。
页面打不开或者返回 502:先看服务状态,gitlab-ctl status看各组件是否都在跑。资源不足是最常见的原因,尤其是内存,GitLab 是个吃内存的大户,内存不够时组件会被系统杀掉,表现就是随机 502。查看系统日志确认是否有进程被强制终止的记录。
磁盘满了导致服务异常:GitLab 的日志、制品、备份文件都会持续增长。建议定期清理过期制品,配置日志轮转,备份文件不要留在同一块盘上。磁盘一满,表现五花八门,从无法推送代码到页面白屏都有可能,所以遇到奇怪问题先看一眼磁盘剩余空间,这是我养成的第一个排查习惯。
改完配置不生效:编辑/etc/gitlab/gitlab.rb之后必须执行gitlab-ctl reconfigure,只重启服务是不生效的。这个坑我踩过不止一次。
升级后异常:升级前一定要看版本间的升级路径说明,跨大版本通常不能一步到位,需要按顺序经过中间版本。升级前做一次完整备份,出问题能回退。
备份怎么做:官方提供了备份命令,会打包数据库和仓库数据。要注意的是备份默认不包含配置文件,配置需要单独备份。另外备份文件要异地存一份,同一台机器上的备份在机器挂掉时等于没有。
6.4 我在建仓这件事上踩过的坑,整理成清单
- 建仓时勾了 README,本地又有一堆代码,第一次推送被拒。现在的做法:本地有代码就建空仓库。
- 多平台共用一把 SSH 密钥,换平台时互相覆盖。现在按平台分别命名密钥文件。
.env和密钥文件被推上去了。删除文件不管用,历史里还在,只能重写历史再强推,通知所有人重新 clone。从那以后我在每个仓库都先写好.gitignore。- 提交邮箱和账号邮箱不一致,贡献图上没有我的记录,排查了半天才发现是本地全局配置的问题。
- 自建实例的克隆地址是 IP,换网络后全部失效。改
external_url之后,还得通知所有人改 remote。 - 流水线产物不过期,磁盘被撑满。加了
expire_in之后就没再出过这个问题。 - 保护分支没开,同事直接推到 main 覆盖了别人的提交。现在建仓第一件事就是配保护分支。
- 用
--force推功能分支,冲掉了别人的提交。改成--force-with-lease之后,至少多一次提醒。 - 删除仓库前没做本地备份,删除保留期一过就找不回来了。现在删之前一定先 mirror 一份到本地。
- 升级前没看路径说明,服务起不来,回滚折腾了一晚上。现在升级前先备份,再对着路径文档确认。
6.5 一个容易被忽略的细节:内网时间同步
这一条单独拎出来,因为它造成的问题最难排查。自建环境里,如果服务器的系统时间和实际时间偏差较大,令牌校验、证书校验、CI 任务调度都可能出问题,而且报错信息通常指向别的地方,比如认证失败、证书无效、任务状态异常。
处理方式是配置标准时间同步服务,保证服务器时间准确。排查时先用date看一眼服务器时间,跟你本地时间对一下,差了几分钟以上就要警惕。这个检查不花时间,但能省掉一整个下午的排查。
最后分享一个小技巧:建新仓库时,把我常用的几个模板文件一起提交进去。一个覆盖常见语言产物的.gitignore、一个README.md骨架(写清楚项目用途、启动方式、目录说明)、一个.gitlab-ci.yml最小模板。三个文件加起来不到一百行,但每个新仓库都省下十几分钟,而且能保证团队里每个仓库的结构是一致的,新人接手时看一眼 README 就知道怎么跑起来。我现在是把这三个文件放在一个模板仓库里,建新仓时直接复制过来改改,比每次从零想一遍要舒服得多。