1. 为什么要先测 SSH 连接再动手拉代码
很多人拿到 Gerrit 地址后的第一反应是直接把git clone粘进终端,结果卡在密码输入、卡在Permission denied (publickey)、卡在Connection refused,然后开始怀疑仓库地址错了、网络不通、权限没开。我在实际带人的过程中发现,九成的"拉不下来代码"其实在 SSH 这一层就已经能定位,只是大家跳过了这个前置校验步骤。Gerrit 的代码拉取走的是 SSH 协议,它不像普通的公开仓库那样匿名可读,你的身份凭证完全依赖一对本地私钥和你在 Gerrit 账号里登记的公钥是否匹配。这条链路上一共有四个环节:本机私钥存在且权限正确、SSH 客户端能正确选到这把私钥、网络能连到 Gerrit 的 SSH 端口、Gerrit 侧认可这把公钥对应的账号。任何一环断了,git clone都会以一个很含糊的报错结束。
所以标题里说的"测试 SSH 连接并下载代码",本质上是一套分层验证的方法论:先用最低成本的方式确认身份和通道都通了,再去做耗时的仓库克隆。这么做有两个非常现实的好处。第一,排错效率天差地别。git clone失败时你面对的是一个复合错误,而ssh -T或ssh -p 端口 主机失败时你能看到明确的拒绝原因,比如是密钥没被接受(Permission denied (publickey)),还是端口根本连不上(Connection timed out),还是主机名解析失败(Could not resolve hostname)。这三种错误的排查方向完全不同,混在一起看就只能瞎猜。第二,避免反复输入密码或者被反复拒绝导致账号被临时限制。有些 Gerrit 实例对认证失败次数是有阈值的,连续失败多次会锁定一段时间,这时候你再去调反而更被动。
这篇内容适合三类人看:刚接手一个需要 Gerrit 评审流程的项目、之前一直用 HTTPS 方式拉代码现在要切到 SSH、以及已经能拉代码但想搞清楚背后到底发生了什么的人。我会按"准备密钥 → 验证连通性 → 定位失败原因 → 真正拉代码 → 处理多仓库场景"的顺序讲,每一步都给出可以直接抄的命令和判断依据。文中涉及的主机名、端口、项目名一律用占位符表示,你替换成自己环境的实际值即可。这里先给一个整体判断表,让你知道走完这套流程后,每种现象对应什么结论。
| 验证动作 | 期望结果 | 出现异常说明什么 |
|---|---|---|
ssh -T -p 端口 用户@主机 | 返回欢迎语或明确的账号信息 | 密钥、用户名或端口有问题 |
ssh -v详细日志 | 能看到Offering public key且被接受 | 客户端没选对密钥 |
git ls-remote | 列出远端分支引用 | 仓库路径或读权限有问题 |
git clone | 正常落盘 | 前序环节已全部通过 |
需要强调一点,Gerrit 的 SSH 端口不一定是 22。很多团队为了避免和系统 SSH 冲突,会把 Gerrit 的 SSH 服务放在 29418 这类高位端口上。这是新手最容易忽略的细节,后面会专门讲。
2. 密钥准备:一对能用的钥匙比什么都重要
2.1 先确认本机到底有没有可用密钥
在动手生成新密钥之前,先看看现有的。打开终端执行:
ls -al ~/.ssh正常情况下你会看到id_rsa、id_ed25519这类文件,以及对应的.pub公钥文件。如果目录不存在或者里面空空如也,那就说明从来没配过,需要生成。如果已经有一对密钥但你不确定它有没有在 Gerrit 上登记过,也别急着删,先拿它去试连通性,试不通再考虑新增。
这里有个很实际的提醒:不要因为新项目就无脑覆盖已有的id_rsa。很多人第一次配 Gerrit 时直接ssh-keygen一路回车,把原来的密钥覆盖了,结果原本能访问的其他代码仓库全部失效。正确做法是给新用途单独起名,比如id_ed25519_gerrit,然后用配置文件指定哪个主机用哪个密钥,这一点在 2.3 节展开。
2.2 生成密钥时那几个交互提示该怎么答
现代环境建议用 ed25519,比 RSA 更短更安全,兼容性在近几年的服务端上也基本没问题:
ssh-keygen -t ed25519 -C "你的邮箱或工号标识" -f ~/.ssh/id_ed25519_gerrit执行后会问你两件事。第一件是 passphrase,也就是给私钥再加一层密码。从安全角度讲应该设,但从日常使用角度讲,如果你的机器本身有登录密码、也不是共享环境,留空可以让后续的git clone完全不打断。我个人的做法是:个人开发机留空,公共或共享机器一定设。第二件是确认路径,如果-f已经指定了就不会再问。
生成完成后两个文件:id_ed25519_gerrit是私钥,绝对不能外传;id_ed25519_gerrit.pub是公钥,内容需要贴到 Gerrit 账号设置里。用下面命令把公钥内容打出来复制:
cat ~/.ssh/id_ed25519_gerrit.pub复制的时候注意要完整复制一整行,从ssh-ed25519开头到末尾的注释,中间不能断行、不能多空格。我见过好几次粘贴时首尾被截断导致认证失败的案例,尤其是用某些终端复制长文本时。粘贴到 Gerrit 的 SSH Keys 设置页后保存,通常立即生效,不需要重启什么服务。
2.3 用 config 文件管理多把钥匙,避免选错
当你本机有多把密钥(比如一把给内部代码平台、一把给个人项目),SSH 客户端默认会按顺序尝试~/.ssh下的默认文件名。如果 Gerrit 用的不是默认名,就会出现"明明确实配了公钥却还是被拒"的经典问题。解决办法是写~/.ssh/config:
Host gerrit-review HostName 代码平台主机地址 Port 29418 User 你的账号名 IdentityFile ~/.ssh/id_ed25519_gerrit IdentitiesOnly yes这里每一行的作用值得说清楚。Host是你自己起的别名,后面所有命令都可以用gerrit-review代替一长串地址,这也是避免命令写错的实用技巧。HostName是真实地址,可以填域名也可以填 IP。Port就是前面强调过的、Gerrit SSH 服务的实际端口,千万别想当然写 22。User是你在 Gerrit 上的账号名,注意它不一定是邮箱前缀,也可能是工号,以账号设置页显示为准。IdentityFile指定用哪把私钥。IdentitiesOnly yes这一行非常关键,它的意思是只使用我指定的这把钥匙,不要去尝试 ssh-agent 里缓存的其他钥匙。没有这一行,客户端可能先拿别的钥匙去试,服务端拒绝次数一多就会直接断开,表现出来就是莫名其妙的Too many authentication failures。
如果 config 里已经有同名 Host 段,直接修改即可,不要重复添加两个同名段,SSH 只会读第一个匹配的。
2.4 文件权限这件小事,错了就是连不上
SSH 对私钥文件权限非常敏感。如果私钥对其他用户可读,客户端会直接拒绝使用它,报bad permissions或者干脆跳过。标准做法:
chmod 700 ~/.ssh chmod 600 ~/.ssh/id_ed25519_gerrit chmod 644 ~/.ssh/id_ed25519_gerrit.pub chmod 600 ~/.ssh/config700表示只有自己能进这个目录,600表示只有自己能读写文件。这个坑在 Windows 上通过 WSL 或者从别处拷贝密钥文件时特别容易踩到,复制过来的文件权限往往是 777,必须手动收窄。判断方法很简单,ls -l看第一列权限位,私钥那一行不该出现r或w给 group 和 others 的标记。
3. 测试连接:一条命令看清四层信息
3.1 最直接的连通性测试命令
准备好密钥之后,第一步不是 clone,而是:
ssh -T -p 29418 你的账号名@代码平台主机地址如果配了 config 别名,可以简化成:
ssh -T gerrit-review-T的意思是禁用伪终端分配。这一点很多人不理解为什么要有。普通的 SSH 登录会申请一个终端会话,而 Gerrit 的 SSH 服务不是给你一个 shell 用的,它只接受特定命令。如果不加-T,有些环境会因为没有终端而报错或挂住。加上之后,连接建立后 Gerrit 会返回一段欢迎信息,通常包含**** Welcome to Gerrit Code Review ****这类字样,有时还会带上你的名字和账号信息。看到这段欢迎语,就说明身份认证这一步已经彻底通过了。
如果返回的是Permission denied (publickey),别慌,这恰恰是最有价值的信息,它明确告诉你 SSH 通道是通的、端口是开的,问题出在密钥不被接受。下一节专门拆解这种情况。如果返回Connection timed out或Connection refused,方向就转向网络和端口了,与密钥无关。
3.2 首次连接时的指纹确认要看清
第一次连一台新主机时,SSH 会提示类似The authenticity of host ... can't be established. Are you sure you want to continue connecting?并要求输入 yes。这是主机指纹校验,属于正常的安全机制。这里的原则是:如果团队有公布主机的公钥指纹,一定要核对一致再输 yes;如果没有公布,至少确认你连的地址确实是内部通知里给的那个。输 yes 之后指纹会写入~/.ssh/known_hosts,之后再连就不再询问。
反面情况是:某天突然又提示指纹变了,说REMOTE HOST IDENTIFICATION HAS CHANGED。这可能是服务端换了机器或重装了,也可能是中间链路有问题。不要习惯性地删掉 known_hosts 了事,先确认服务端是否真的变更过,问清楚再处理。处理方法是删掉known_hosts里对应那一行,重新连接确认新指纹。
3.3 用 -v 把认证过程摊开来看
当连接失败且原因不明时,-v是最好用的工具:
ssh -vT -p 29418 你的账号名@代码平台主机地址加上-v会打印大量调试信息,信息量大但关键就那么几行。重点盯三个位置:
debug1: Connecting to ... port ...这一行确认地址和端口解析正确,如果这里显示的端口不是你期望的,说明 config 没生效或命令行没覆盖。debug1: Offering public key: ...出现几次就代表尝试了几把钥匙。看它后面紧跟的是哪个文件名,如果根本不是你准备的那把,说明IdentityFile或IdentitiesOnly没配好。debug1: Server accepts key或Authentication succeeded表示这把钥匙被接受了。反之如果每把都是Server accepts key之后又跟Permission denied,那问题在服务端对这个公钥的登记,不在本机。
信息太多看不清时可以叠加-vvv拿更细的日志,但通常-v就够了。实际排查中,靠着Offering public key这一行定位到"客户端一直在试另一把钥匙"的情况非常多,这也是我把IdentitiesOnly yes单独拎出来讲的原因。
3.4 一条命令同时验证身份和命令通道
Gerrit 的 SSH 接口除了认证,还支持一些管理命令。认证通过后可以直接跑一个轻量命令验证命令通道是否可用:
ssh -p 29418 你的账号名@代码平台主机地址 gerrit version或者查一下自己的账号信息:
ssh -p 29418 你的账号名@代码平台主机地址 gerrit query --format=JSON status:open limit:1后一条会返回一小段 JSON,能正常返回就说明不仅认证通过,连 Gerrit 的业务命令都在正常响应。这比只看欢迎语更彻底。注意gerrit后面跟的version、query都是子命令,实际可用的命令集合以你那套环境的版本为准,不同版本可能有差异。如果不确定支持哪些,可以先只跑-T看欢迎语,那是最保险的。
4. 认证失败的分层排查链路
4.1 先分清是哪一类拒绝
面对Permission denied (publickey),很多人第一反应是重新生成密钥重新贴公钥,这往往是无用功。正确做法是分层定位。下面这张表是我实际排错时按顺序走的检查清单:
| 检查项 | 具体动作 | 结论 |
|---|---|---|
| 客户端选了哪把钥匙 | ssh -vT ...看Offering public key | 选错则改 config |
| 公钥是否真的登记 | 对比本地.pub内容与账号设置页 | 不一致则重贴 |
| 账号名是否正确 | 检查User或命令里的用户名 | 错误则修正 |
| 服务端是否认这把钥匙 | 日志中是否出现 accepting | 不认则查账号状态 |
按这个顺序走,绝大多数情况在三步内就能收敛。上面每一步都能给出明确的"是/否"结论,而不是模棱两可的猜测。
4.2 密钥登记环节最容易出的三种错
第一种,贴错了密钥。本机有多把公钥,复制时拿错了文件。判断方法:把账号设置页里登记的公钥内容与本地cat 文件.pub的输出做逐字符比对,或者用ssh-keygen -lf算出指纹来比:
ssh-keygen -lf ~/.ssh/id_ed25519_gerrit.pub同一把密钥的指纹是唯一的,比人眼比对长字符串可靠得多。有些平台设置页也会显示指纹,直接对指纹最快。
第二种,粘贴被截断或带入了多余字符。从终端复制时不小心带上了提示符,或者复制了多行。公钥必须是一整行。判断方法:本地用wc -c看长度,ed25519 公钥一般在 80 到 100 字符之间,明显偏短基本就是截断了。
第三种,公钥贴到了错误的账号下。多人共用一台机器或者有多套环境时,本地密钥对应的账号和你以为的账号不是同一个。这种问题表现为别的都正常,就是没有目标仓库的权限。排查时用-v确认认证通过,然后看欢迎语里显示的账号名是不是你以为的那个。
4.3 账号名而不是邮箱,这个细节坑过很多人
Gerrit 的 SSH 认证里,用户名指的是你在 Gerrit 系统里的username字段,而不是邮箱、不是显示名。有些团队的账号名是工号,有些是姓名拼音,与邮箱前缀可能一致也可能完全无关。如果你用邮箱去连,会得到Permission denied,而且日志里看起来像是密钥问题,非常容易误判。
确认方法:登录 Gerrit 的网页端,进个人设置页看 Username 那一栏。如果那一栏是空的,说明这个账号还没设置用户名,SSH 方式就无法使用,需要先设置。这一点在自助注册的实例上很常见,值得先确认一眼再折腾密钥。
4.4 端口不通与密钥失败是两码事
如果报错是Connection timed out、Connection refused、No route to host,那和密钥毫无关系,别再动密钥了。区分技巧很直接:认证失败时你至少能看到服务端回了Permission denied,说明 TCP 连接是建立的;而超时或拒绝说明连接压根没建立。
排查顺序:先用ping 主机地址或者nc -vz 主机地址 29418看目标端口是否可达。nc在多数 Linux 和 macOS 上都有,Windows 上可以用 PowerShell 的Test-NetConnection代替。如果端口不通,先确认端口号对不对——再次强调 Gerrit SSH 端口不一定是 22。如果端口对但连不上,那就是网络策略层面的事,需要找负责网络环境的同事确认,不要在本地反复试。
还有个隐蔽情况:主机名能解析但解析到了错误的地址(比如内网和外网两套地址)。用nslookup 主机地址或ping看一眼解析结果,和团队给的地址对一下。这个坑在切换办公地点或接入不同网络后特别容易出现。
4.5 一个完整的手把手排查示例
假设现象是ssh -T报Permission denied (publickey)。我会这样走一遍:
第一步,跑ssh -vT -p 29418 账号@主机,在输出里找到Offering public key那几行。假设发现它尝试的是id_rsa,而我的 Gerrit 密钥是id_ed25519_gerrit,那问题定位完成:客户端没选对钥匙。解决办法是在 config 里加IdentitiesOnly yes并确认IdentityFile指向正确文件。
第二步,如果日志显示确实尝试了id_ed25519_gerrit但依然被拒,那就登网页端核对公钥指纹。假设发现网页上登记的是旧机器的公钥,问题同样定位完成:重新贴当前这把。
第三步,如果公钥完全一致、账号名也正确,但还是被拒,那就查账号状态,比如是否被停用、是否用户名未设置。这一步就不是本机能解决的了。
走完这三步,基本能覆盖所有认证类失败。整个过程的关键是每一步都从日志里找证据,而不是靠猜。
5. 从验证到真正把代码拿到本地
5.1 先确认仓库存在且有读权限
SSH 通道验证通过之后,先别急着 clone 整个仓库。用一个轻量命令确认仓库路径正确、你有读权限:
git ls-remote ssh://账号@主机:29418/项目路径这条命令只拉取远端的引用列表,不下载文件内容,速度快,输出是分支和标签的哈希值列表。能列出来就说明路径正确、权限足够、SSH 也没问题;如果报not found或权限错误,问题在仓库路径或权限,与 SSH 配置无关。这一步的价值在于把"连接问题"和"仓库问题"彻底切开,避免 clone 失败时又要重新判断一遍。
仓库路径的写法要注意,它通常不是简单的一个名字,而是带层级的分组路径,形如某分组/某项目。这个路径在 Gerrit 网页端的项目页面里能看到,直接复制最稳妥。路径写错时错误信息往往也是not found,和权限不足不容易区分,所以务必从网页端抄准确。
5.2 clone 命令的两种写法和该选哪种
拿到正确路径后,clone 有两种写法:
# 写法一:完整 URL git clone ssh://账号@主机:29418/某分组/某项目 # 写法二:利用 config 别名,配合 scp 风格路径 git clone gerrit-review:某分组/某项目写法一好处是自解释,缺点是要重复输入账号、主机、端口,命令长且容易打错。写法二依赖 2.3 节的 config 配置,命令简短,切换项目时只改路径部分,实际使用中我更推荐这种。注意写法二里的冒号不能省,它表示分隔主机和路径。
如果仓库体积很大,可以加上--depth 1只拉最近一次提交,先快速拿到代码看结构,需要完整历史时再git fetch --unshallow补齐。这个技巧在只想看看代码长什么样、并不打算提交的场景下很实用,能省掉大量等待时间。
5.3 分支与提交规范:clone 完只是开始
代码落到本地之后,还有一步很关键——确认你们团队的提交流程。Gerrit 的核心是"评审后合入",它不是直接推到分支,而是推到refs/for/分支名。很多刚从其他平台转过来的人在这里翻车:照常git push origin main,结果要么被拒,要么绕过了评审。
正确的推送形式是:
git push origin HEAD:refs/for/目标分支名这条命令的含义是把当前提交推到评审队列,而不是直接进分支。如果团队配置了 Change-Id 机制,还需要在提交信息里带上Change-Id,这个通常由提交钩子自动生成。钩子需要手动安装:
# 从仓库里取钩子脚本放到 .git/hooks 下 scp -p -P 29418 账号@主机:hooks/commit-msg .git/hooks/ chmod +x .git/hooks/commit-msg注意这里scp的端口参数是大写-P,和ssh的小写-p不同,这个大小写差异坑过不少人。装好钩子后,每次git commit都会自动在提交信息末尾追加一行Change-Id,后续的评审更新都靠它来关联同一组修改。
这套机制初次接触会觉得繁琐,但理解之后就明白它的价值:Change-Id让同一个改动的多个版本能在评审系统里聚合成一条记录,评审意见、修订历史都串在一起,而不是散成一堆互不相关的提交。如果你后续要频繁提交修改,这个钩子是必装项,别等到推送被拒才回头补。
6. 多仓库、多环境下的实用配置心得
6.1 多个项目共用一套认证
当你需要拉取同一平台下的多个仓库时,不要在每条 clone 命令里重复账号和端口。用 config 别名后,所有仓库统一写成gerrit-review:分组/项目A、gerrit-review:分组/项目B,认证配置只维护一份。这样即便后续端口变了、账号名调整了,也只需要改一处。
如果不同项目分属不同的 Gerrit 实例(地址不同),就在 config 里写多个 Host 段,各用各的别名和密钥,互不干扰。这就是前面强调"不要覆盖默认密钥"的原因——多实例场景下,每个实例配一把独立密钥是最清晰的做法,出问题时能一眼看出是哪个实例的配置有问题。
6.2 换了机器怎么快速恢复
换电脑或者重装系统后,最省事的做法是把~/.ssh目录里那把私钥和config文件安全地迁移过去,然后重新设置权限(chmod 600)。私钥不用重新生成,公钥也不用重新登记,直接可用。但前提是迁移过程本身要安全,不要通过聊天工具、邮件这类渠道传输私钥。
如果确实拿不到原私钥,那就重新生成一对,把新公钥贴到账号设置里。旧公钥记得清理掉,避免账号上挂着一堆来历不明的密钥——这既是安全习惯,也能避免将来排查时被旧密钥干扰。
6.3 几个高频小问题的快速对照
| 现象 | 大概率原因 | 处理方式 |
|---|---|---|
Permission denied (publickey) | 密钥未登记或选错 | 按第 4 节分层排查 |
Connection timed out | 端口错或网络不通 | 核对端口,测端口可达性 |
Could not resolve hostname | 主机名写错或解析异常 | 核对地址,查解析结果 |
Too many authentication failures | 客户端试了太多钥匙 | 配IdentitiesOnly yes |
| clone 报 not found | 仓库路径写错或无权限 | 从网页端复制完整路径 |
| push 被拒 | 推到了普通分支而非评审队列 | 改推refs/for/分支 |
6.4 我踩过的两个印象深刻的坑
第一个是关于端口。早期我一直以为 SSH 就是 22,配置时没写端口,结果ssh连上去返回的是另一台服务器的欢迎语,根本不是 Gerrit。查了半天才发现 Gerrit 在 29418,而我连的 22 是系统 SSH。这个教训是:连接成功不等于连对了地方,欢迎语的内容一定要看一眼,确认是目标服务。
第二个是关于密钥选择。有段时间我的机器上装了密钥管理工具,它会把多把密钥加载到 ssh-agent 里。结果ssh -T时客户端按顺序试了五六把钥匙,全被拒,最后报Too many authentication failures,看起来完全不像密钥问题。加上IdentitiesOnly yes之后立刻正常。这类问题最迷惑人的地方在于,它伪装成了"认证失败",但真正的问题是客户端太"热情",试了不该试的钥匙。
把测试 SSH 连接这一步单独拿出来做,看似多花了一分钟,实际上省掉的是后面反复试错的半小时。养成先验证通道、再拉代码、再推评审的顺序,后面无论换环境还是帮别人排查,思路都会清楚很多。