SVN这事吧,平时安安静静,一旦报错就是全员坐等。前两天我就碰到这么一档子事:上午还有人在往仓库里提交代码,下午整个项目组的新版本全都拉不下来,客户端清一色报svn: E170013: Unable to connect to a repository at URL。有人ping了一下服务器,发现域名解析直接挂了。那会儿我们SVN服务器用的是一串内网主机名,比如svn://svn-legacy.local/app-android,主机名一旦解析不了,所有工作副本就像断线的风筝。最后的解决办法很简单,就是把SVN地址改成IP:svn://192.168.10.20/app-android。
听起来无非是换一个地址重新连接,但实际操作起来,坑比想象中多。relocate会掉哪些链子,服务端要不要改监听,改完为什么弹证书警告,为什么明明同一个仓库却报UUID不匹配……这些如果你没提前搞清楚,很容易在改地址的路上把工作副本搞坏,甚至把仓库搞出精神分裂。这篇就把整个流程拆开揉碎讲清楚,覆盖命令行和TortoiseSVN的两种改法、服务端IP绑定、周边配置清单和常见问题排查,适合运维、开发组长和所有被SVN坑过的人参考。
1. 这个需求是怎么来的:什么情况下要把SVN地址改成IP
1.1 最常见的三种场景
第一个典型场景就是开头说的内网DNS抽风。很多公司搭建SVN时图省事,直接用机器名注册到内网DNS,比如叫svn-legacy.local,客户端全用主机名访问。但内网DNS这东西一旦重启、换IP、或者机器换了网卡,解析记录就容易乱,或者干脆没了。主机名连不上,全员被卡在门口,最快的办法就是绕过DNS,直接对着IP干活。
第二个场景是机房迁移或者服务器IP变更。原来SVN地址写在svn://192.168.1.50/repos/project-a,后来这台机器挪到新网段,IP变成了192.168.10.20。这种时候你要改的就不只是客户端了,服务端如果之前绑定过旧IP,防火墙规则、值班脚本、CI里的地址,全部要跟着动。说是“改一个IP”,实际上是改一整条链路。
第三个场景是服务器初始配置时用了localhost。很多人在自己电脑上装了SVN服务做测试,安装时随便填svn://localhost/repos,后来要把仓库开放给同事用,才想起来别人不可能访问你的localhost,必须换成本机在局域网里的IP。这种“从localhost改到IP”的需求其实非常普遍,只是多数人一开始没想到。
1.2 别急着改:先分清“改地址”涉及哪几层
我见过太多人一听说地址要变,直接把自己工作副本删了重新checkout,或者用svn switch去试图切换服务器。其实“把SVN地址改成IP”这句话拆开来至少包含四层,缺一层都会出问题。
第一层是客户端工作副本里的仓库根URL。你本地svn info里显示的URL就是它,它记录在.svn目录的元数据里,不是写死在代码里的。改这一层有专用工具,叫relocate,后面详细说。
第二层是服务端的监听配置。svn://协议的svnserve默认监听所有网卡的3690端口,这类不用改。但如果你用Apache+SVN模块提供http://或https://访问,或者用图形化管理工具让服务绑定在某个具体IP上,IP换了之后监听配置不更新,新地址依然访问不了。
第三层是认证、证书和防火墙。IP和主机名在客户端眼里是两个完全不同的“服务器”,之前存的用户名密码不会自动带过去,HTTPS证书如果只签了域名也会报警告。
第四层是工作区之外的“隐性地址”。比如Jenkins构建脚本、代码评审工具、文档里的仓库地址、别人机器上的SVN配置,甚至是一个以仓库路径为参数的老备份脚本。这些不会因为你改了本地工作副本而自动同步,必须单独排查。
这四层都过一遍,改地址这道工序才算真正干完。
2. 先搞懂机制:SVN地址怎么组成,relocate为什么能原地切换
2.1 SVN地址的四种协议形态
SVN访问仓库的URL常见形态有四种:svn://、http://、https://、file:///。另外还有一个很少见的svn+ssh://。
svn://是svnserve独立服务的默认协议,端口3690,轻量、快,适合内网。http://和https://走的是Web服务器(Apache,或者某些可视化服务端),优点是可以配合WebDAV、权限插件和HTTPS证书。file:///最直接,就是访问本机磁盘上的一个目录,适合单机练习和仓库维护脚本临时复用。
地址格式一般是协议://主机:端口/仓库根路径/具体仓库名。比如:
svn://192.168.10.20/app-android https://192.168.10.20/svn/app-android把主机名改成IP,本质上只改URL里的“主机”这一小段。但SVN客户端如何判断新旧地址指向同一个仓库,它靠的不是主机名字面相同,而是一个叫UUID的东西。
2.2 relocate的内部逻辑:改的是工作副本的元数据,不是代码
svn relocate内部做的事情非常纯粹:它把工作副本.svn目录里记录的仓库根URL前缀替换成新的URL,同时校验新旧仓库的UUID是否一致。它不会重新拉取代码,不会比较文件差异,更不会动你本地未提交的修改。所以即使你有一堆半成品改动没提交,也可以放心执行relocate,改完之后本地文件一个字都不会少。
这一点比“删掉重新checkout”优势明显太多。一个大型项目的完整历史可能几十GB,重新checkout一次少说半天,而且本地没有版本化的配置、生成文件、IDE设置都要重来一遍。relocate过程是秒级的,你甚至感觉不到它做了什么,只有svn info里的URL变了。
2.3 为什么不能拿switch代替relocate
很多人一看到svn switch就觉得“切换地址”就该用它,结果报了错:Unable to connect to a repository at URL ...。
switch和relocate虽然都带“切换”两个字,但完全不是一回事。svn switch用于切换工作副本到同一个仓库中的另一个目录或分支,比如从trunk切到branches/feature-x,它要求切换前后的URL在同一个仓库里,主机、端口、仓库根都不得改变。而relocate专门用于“整个仓库的服务位置变了”,比如从旧主机名换到新IP,或者从svn://换成https://,这时候仓库的UUID不变,只是它所在的地理位置变了。
用switch去改host,等于让仓库“分身”到另一个服务器上去找目录,仓库UUID对不上,当然要报错。记住一句话:改分支用switch,改服务器用relocate。
2.4 UUID:可以安全切换的“身份凭证”
每个SVN仓库在创建的时候会生成一个全局唯一的UUID,就像人的身份证号。工作副本里也记录了它所跟踪仓库的UUID。relocate时,SVN会拿新地址指向的仓库UUID,和你工作副本里存的UUID做比对。
如果UUID一致,说明新旧地址指向的是同一个仓库,你只是换了扇门进去,历史、版本号、提交记录全部对得上。如果UUID不一致,说明这根本就是两个仓库,哪怕路径长得像亲戚也不行,relocate会拒绝执行,并且抛出一段很明确的信息:The repository at 'xxx' has uuid 'uuu', but the working copy has UUID 'vvv'。
出现这个报错,八成不是普通“改IP”,而是仓库本身已经被迁移成了另一个新仓库。后面第五节我会专门讲怎么处理。
3. 动手实操:三种方法把SVN地址改成IP
3.1 方法一:命令行relocate(最推荐)
先看一下工作副本当前URL和UUID,免得改完自己都不确定是不是改对了:
svn info输出里能找到URL: svn://svn-legacy.local/app-android和Repository UUID: 3f2a1c...,记下来。然后执行relocate,给一个新地址:
svn relocate svn://192.168.10.20/app-android注意,SVN的话术有版本差异。SVN 1.7以前的版本用的是:
svn switch --relocate OLD_URL NEW_URL1.7之后官方把relocate从switch里拆出来了,推荐用svn relocate。如果你的工作副本里有外部引用或者多个不同URL前缀,可以指定旧前缀和新前缀:
svn relocate svn://svn-legacy.local svn://192.168.10.20这样它会把工作副本中所有以旧前缀开头的URL批量替换。执行完之后再跑一次svn info,确认URL已经是IP地址。然后随便挑一个文件执行svn status,能正常显示修改状态就说明连接没问题。
这里有个很容易忽略的点:relocate不能在一个只检出了部分目录的工作副本上执行,比如你当初只checkout了某个子目录。SVN识别的是仓库根URL,而子目录工作副本记录的也是完整URL前缀,所以通常没问题。但凡你用了--depth稀疏检出,也一样可以relocate,它改的还是元数据里的前缀。
3.2 方法二:TortoiseSVN图形界面
Windows开发者用TortoiseSVN的比较多,图形化改地址很简单。在工作副本根目录上右键,找到TortoiseSVN→Relocate...,弹出窗口里最上面是仓库Root URL文本框,把里面的旧主机名换成IP,点OK就行。
有一个细节:新版TortoiseSVN的右键菜单里,Relocate...可能藏得比较深,不在最外层。如果你没看到,就进入TortoiseSVN→Properties或者直接在repo-browser里操作。更省事的办法是直接在根目录右键选择SVN Checkout...对话框里查看当前URL,然后终止操作,另起一个命令行用svn relocate。
图形界面比命令行的好处是可以忽略大小写、自动补全,缺点是那些把所有配置都藏在“自动保存认证”里的用户,改完之后容易因为SVN提示重新认证而不知所措。这个后面说。
3.3 方法三:服务端视角——把服务真正绑定到IP
如果客户端全部改成了新IP,服务端却还只监听旧地址,那等于白改。先确认你的服务怎么提供的。
svnserve方式最简单。启动命令通常是:
svnserve -d -r /data/svn/repos默认监听所有IP的3690端口,一般不需要额外配置。如果你在svnserve.conf里见过类似listen-host = 192.168.10.20这样的配置,就要检查这段参数是否指向了新IP,必要时删掉或者改成0.0.0.0让所有网卡都监听。
Apache + mod_dav_svn 的方式,要看虚拟主机配置里的ServerName和Listen,IP变了以后如果服务只绑定在旧IP上,同样需要改。Windows下如果用了图形化的SVN服务端,通常在控制台界面的“服务器属性”里能看到绑定的IP地址列表,把新IP加进去并且重启服务。
还有一类场景是服务器做了端口转发,比如公网访问时把443端口映射到内网某台机器的SVN服务。这种时候“改成IP”往往改的是NAT映射目标,要去防火墙或者路由设备上调整映射关系,光改SVN配置是无效的。
3.4 方法四:最干净的方式重新checkout
如果本地工作副本已经很久没更新、改得乱七八糟,或者你把relocate玩坏了,又或者仓库本身已经迁移成新UUID根本无法relocate,就别挣扎了,直接重新checkout往往更省心。
前提是:先把本地未提交的改动处理好。检查一遍svn status,把需要的文件手动提交,或者用svn diff > changes.patch导出补丁,再把没有被版本控制的文件备份到临时目录。确认无遗漏之后,用新IP地址重新checkout一次:
svn checkout svn://192.168.10.20/app-android新checkout的代码是干净的,不会带旧服务器时代的元数据残留。代价是全部历史都要重新下载,项目小还好,项目大就要盯一会儿进度条。所以我一般只在工作副本元数据损坏、或者仓库UUID对不上的时候才建议走这条路。
四种方式的取舍,给个直观对比:
| 方式 | 成本 | 是否影响本地改动 | 适用场景 |
|---|---|---|---|
| svn relocate | 秒级,不下载数据 | 不影响 | 大多数情况,服务端地址变了但仓库没换 |
| 重新checkout | 慢,重新下载全部历史 | 需先备份 | 仓库UUID变了、元数据损坏、工作副本太乱 |
| 改服务端监听/防火墙 | 取决于具体配置 | 不影响,但要重启服务 | 服务端只绑定了旧IP |
| 用switch去改服务器 | 不适用 | 会报错 | 千万不要这么做 |
4. 改完地址后的连锁反应:认证、证书、防火墙、团队同步
4.1 重新认证:地址变了,之前存的账号密码为什么失效
很多人relocate完,第一次提交或者update,SVN突然弹出认证窗口,以为账号密码错了。其实不是,地址从主机名变成IP,SVN会把新地址当作一个“独立的服务器身份”,之前按主机名保存的认证信息不会自动匹配到IP上。
这和安全策略有关,SVN用URL的scheme、host、port来区分认证条目。要么你重新输一次账号密码并勾选保存,要么在命令行加上--username参数主动指定。如果你管理多台机器,最稳妥的办法是让团队成员统一在变更后的第一次访问时,重新输入一次账号并选择保存。
4.2 HTTPS证书与IP不匹配的处理
如果你的SVN地址是https://开头,改成IP后大概率会碰到证书问题。因为之前证书是给svn-legacy.local这个域名签发的,或者自签证书里的有效期、用途都按域名设置,而你现在访问的是192.168.10.20,证书里的主机名对不上,客户端会给出类似“证书主机名不匹配”的警告。
处理方式有几种:对于自签证书的内部服务器,可以在客户端确认证书指纹后临时接受。更正规一些的做法是重新生成一张包含新IP作为Subject Alternative Name的证书,覆盖到服务端。如果你压根不用HTTPS,只需把协议换成svn://就能绕开证书那一堆麻烦,前提是网段可信、不跨公网。
4.3 防火墙与监听地址核查
svn://协议要通3690端口,http/https要通80/443。改完IP还要确认服务端防火墙的入站规则,特别是IP变更后规则还是不是针对新IP的。Linux下常见命令是:
firewall-cmd --permanent --add-port=3690/tcp firewall-cmd --reloadWindows服务器则在“高级安全Windows Defender防火墙”里检查入站规则。如果你发现新IP连不上,先在服务器本机svn list svn://127.0.0.1/repos测试,能通说明服务本身没问题,再去查防火墙。
4.4 工作区外的“地址清单”:CI、脚本、文档、外部引用都要同步
本地工作副本改完只是第一步,公司里其他系统和脚本里很可能还存着旧的svn://svn-legacy.local/...:
- 持续集成工具里的代码地址,比如Jenkins里配置的SVN URL。
- 自动化部署脚本、定时备份脚本里硬编码的仓库路径。
- 项目文档、Wiki、Onboarding文档里给新人抄的checkout命令。
- 版本控制里的
svn:externals属性,如果引用了同一个仓库的其他目录,relocate时通常会一并处理;如果外部定义指向的是另一个仓库,需要额外检查。
建议维护一张“SVN地址依赖清单”,每次变更地址,逐个通知、逐个更新。改IP这事儿,最怕改完发现某条构建流水线还攥着旧地址不放,等发布的时候才炸出来。
5. 常见问题与排查实录
5.1 relocate报UUID不匹配
报错字符串大概长这样:
svn: E200009: The repository at 'svn://192.168.10.20/app-android' has uuid '35a9...', but the working copy has UUID '7e3b...'这说明新地址指向的仓库和你本地之前连的仓库根本不是同一个。可能原因是服务器迁移时用了svnadmin dump/load重新导了一个新仓库,导致UUID变了;也可能是有人新建了一个同名仓库。
解决办法有三种:如果你确定要的是新仓库,那就接受现实,重新checkout,旧的本地改动先备份好。如果你确定新仓库其实是旧仓库搬家来的,只是UUID变了,可以在服务端把新仓库的UUID改成旧的,命令是:
svnadmin setuuid /data/svn/repos 7e3b...改完UUID,relocate就能通过了。不过要小心,如果这个新仓库已经有不少新提交、其他同事也连上去了,一改UUID所有人都会经历一次“仓库身份认知”的震动,所以这种操作要谨慎,最好在改动前和团队确认。
5.2 改完地址连接超时或拒绝
先确认新IP能不能ping通,再确认端口是否开放。可以在客户端机器上用一个简单的网络测试:
telnet 192.168.10.20 3690如果通,再看SVN服务本身是否监听在新IP上。服务端执行:
netstat -anp | grep 3690如果监听的是0.0.0.0:3690或者对应的IP,说明服务正常,问题更可能在防火墙。如果根本没有任何监听,说明svnserve没有启动,或者启动时用了--listen-host指向了旧IP。
还有一种容易被忽略的情况:新IP虽然ping得通,但SVN等待响应时间很长,最后超时。多半不是SVN挂了,而是客户端本地解析新IP时走了代理设置或者网络配置残留。检查一下环境变量http_proxy、no_proxy,以及SVN配置文件里的servers段,确保没有把新IP也代理出去。
5.3 频繁弹认证框,或者账号密码明明对却报认证失败
地址换成IP之后第一次弹认证框是正常的,前面说过了。但如果每次都弹,说明没有把认证信息保存成功。检查两个地方:一是SVN客户端的认证缓存目录,Linux下在~/.subversion/auth/,Windows下在%APPDATA%\Subversion\auth\;二是SVN配置文件中是否设置过store-passwords = no或store-auth-creds = no。
如果你是系统管理员,可以引导大家主动清理旧的认证缓存,避免新旧主机名/地址混在一起造成混淆。清理方式就是把对应host的缓存文件夹删掉,然后重新认证。
另外有一种情况:旧地址上的认证信息是以域名形式保存的,新IP上还没有,而你输入密码时又被旧缓存干扰,比如客户端在多个认证条目里匹配到了错误的用户名。解决办法是在命令行/图形工具里手动指定--username 你的账号,别靠默认。
5.4 外部引用(svn:externals)更新失败
使用svn:externals属性的项目,如果在属性里写的是绝对URL,比如svn://svn-legacy.local/lib/lib-common,那么relocate只改了工作副本的仓库根URL,不一定能自动修改这个属性。出现这种情况,更新外部引用时会继续尝试连旧地址。
解决方式是在relocate时带上旧前缀,让所有以旧前缀开头的URL一起替换:
svn relocate svn://svn-legacy.local svn://192.168.10.20如果此前已经relocate完了,就需要手动编辑属性:
svn propset svn:externals "lib-common svn://192.168.10.20/lib/lib-common" . svn commit -m "Update externals URL after server IP change"这里提醒一句:外部引用尽量在属性里使用相对路径,比如^/lib/lib-common这种形式,以后服务器换IP,外部引用会自动跟着仓库根URL走,省掉一大半麻烦。
5.5 原本用IP,现在想换回域名,反过来怎么操作
换回来的原理和换出去完全一样,只是方向相反。客户端继续用svn relocate svn://域名/仓库路径,服务端如果之前把域名解析配好了就不用动。需要注意的还是HTTPS证书:如果你原来用的自签证书只签了IP,现在换成域名访问,一样会跳证书警告。
如果公司最终目的是“以后域名走DNS、IP不裸奔”,我建议先把证书处理好,再改客户端,最后再清理所有硬编码IP的脚本和CI配置。顺序反了容易造成两个人用两个地址访问同一个仓库,虽然仓库还是那个仓库,但认证记录、缓存、证书信任都容易错乱,排查起来很头疼。
6. 一点实战心得
SVN地址改成IP,技术上真不是难题,难的是在改之前想清楚“哪些东西会因为地址变化而跟着变”。我在实际处理过几轮之后,慢慢养成了一套固定动作:先记录旧URL和仓库UUID,再确认仓库是不是同一个,然后逐个客户端执行relocate,再顺手把CI、脚本、文档里的地址全部替换,最后通知全组用新的IP地址完成一次checkout测试。这套动作下来,踩坑概率会低很多。
另外,内部搭建SVN服务的时候,我建议一开始就把服务器IP固定下来,不要在DHCP动态IP上跑版本控制服务。如果人少用svn://最省心,如果跨部门、跨地域访问比较多,尽早把HTTPS证书方案定下来,省得每次换地址都在证书这一关纠结。真到了要换的那天,你会发现relocate十几秒能完成的事情,最花时间的其实是说服所有团队成员“你别把工作副本删了重新checkout”。
最后送一个实用技巧:改地址之前,先在旧地址上把当前工作副本里所有未提交的修改合并成补丁备份到本地,万一relocate之后发现连接还是失败,你至少不会丢代码。毕竟SVN这东西,稳定的时候你几乎感觉不到它存在,一旦出问题,每个人都突然想起来了。