最近在公司里折腾了一个有点意思的事情:让同一个前端项目同时被SVN和Gitee管着。听起来像两个版本控制系统的“左右互搏”,但实际用起来,这是一套很多团队都会碰到的组合——公司内部的代码规范、历史包袱和审批流程还牢牢绑在SVN上,另一边领导又要代码推到Gitee上做自动化构建、做代码看板,甚至某个组件要开放给外部开发者用。前端项目依赖多、构建产物杂、开发节奏快,在两套版本系统之间来回摆渡代码,如果不提前想清楚方案,随便操作一下就能把两个仓库都搞乱。
这篇文章把我整个折腾过程里的方案选型、配置细节、同步命令和踩过的坑都盘了一遍。适合三类人看:一是公司同时有SVN和Gitee、被迫搞双仓库的人,二是想把SVN项目往Git平台迁移但还没下决心的团队,三是纯粹想搞清楚两套版本系统边界的新手。
1. 为什么一个前端项目会同时需要SVN和Gitee
1.1 先分清SVN和Git到底差别在哪
不把底层模型讲清楚,后面配置起来就是瞎撞。SVN是集中式版本控制,所有历史版本都存在服务器上,工作目录里的.svn文件夹记录的是当前文件与服务器版本的差异信息。你提交代码,实际上是直接往中央仓库写,写完之后服务器上就有了你的变更。Git是分布式版本控制,每个人本地都有一个完整仓库,git commit只是提交到本地,git push才把本地提交推到远端,.git目录里存的是整套对象数据库。
说得直白点,SVN像一个公司大办公室,所有人改同一份共享文档,谁改了什么服务器立刻知道;Git像一个分发给每个人的笔记本,你在自己本子上随便写,写完再把整本笔记的内容同步给别人。前端项目文件多、依赖重、分支操作频繁,Git在本地提交和分支切换上的速度优势非常明显,但集中式的SVN在权限控制和管理审批上有它的历史优势,很多公司的研发流程已经深绑在SVN上,不是说拔就能拔的。
1.2 项目里的真实需求场景组合
我在实际项目里遇到的情况非常典型:前端小组十几个人,代码一直在公司内网的SVN服务器上管理,审批流程、巡检规则、版本发布记录全部围绕SVN的trunk主干来做。但新来的技术总监要求所有前端项目必须能在Gitee上看到最新代码,评审的时候直接在Gitee上看diff、留评论。还有一个组件库要做成开源项目挂到Gitee上供外部使用,但公司内部代码库又不允许直接暴露。
这就出现了“一个项目两套账”的局面:SVN继续作为公司内部代码流转的“法务系统”,Gitee作为展示、协作和自动构建的“外部窗口”。不少团队还面临另一个现实问题——代码查重、安全扫描、CI/CD流水线都已经接在Gitee上,而SVN服务器上的钩子脚本又老又没人维护,两套系统被迫并行。
1.3 为什么不做彻底迁移
很多人第一反应是干脆从SVN迁移到Git Gitee,省得两边同步。我一开始也是这么想的,但深入了解后发现真没那么简单。一是SVN仓库里积累了好几年的历史记录,迁移过程中翻车风险很高,而且公司内部的权限审批流程跟SVN用户组绑定,迁移等于动整个研发基础设施。二是有些后端服务、设计稿、运维脚本这些配套资源还在SVN上,前端项目单独迁走反而跟周边团队割裂。三是SVN服务器由运维统一管理,前端组没有管理员权限,没法说关就关。
所以最终结论是:“共存”不是最优解,但在很多公司是唯一解。既然改变不了平台格局,那就把两套系统的边界管理好。
2. 三种落地方案,选哪种不踩坑
2.1 同目录双仓库共存:最推荐的做法
所谓同目录双仓库共存,就是同一个项目目录里同时存在.svn和.git两个元数据文件夹,SVN和Git各自把对方的关键目录加入忽略规则,互不干扰。日常开发时,你在SVN里正常提交,保留公司流程;同时定期把最新状态commit到Git、push到Gitee。反向操作也一样,Gitee上的MR合入后,把变更拉回本地再提交到SVN。
这个方案最大的好处是开发人员的工作习惯几乎不用改,还是在同一个目录里写代码,不需要在多个目录之间来回搬运文件。我在实际操作中觉得最舒服的一点是,你永远有一个“全量快照”在Gitee上,哪怕公司SVN服务器挂了,本地Git仓库和远程Gitee都还有一份完整代码,抢救起来心里有底。
2.2 git-svn桥接:适合个人体验,不适合团队协作
git-svn是Git官方提供的一个桥接工具,可以用Git命令操作SVN仓库,核心命令是git svn clone、git svn dcommit、git svn rebase。它跟“同时使用两个平台”不太一样,它只是把Git当成SVN的一个客户端外壳,远端仓库还是SVN,并没有真正把代码推到Gitee。
这个方案最大的问题在并行操作时很容易乱。git-svn要求你的Git历史必须和SVN历史线性对应,本地一旦用了git rebase改写过提交,再 dcommit 到SVN就会产生各种诡异的冲突。而且它不支持多端同时操作,团队里一个人用了git-svn,其他人还是用TortoiseSVN,两者很容易把SVN服务器的版本线搞乱。我自己的体会是,git-svn拿来个人尝鲜可以,千万别在团队里当正式方案用。
2.3 服务器脚本自动同步:重武器,看运维资源
进阶玩法是在SVN服务器上配置post-commit钩子,每次有人提交SVN,钩子脚本自动把变更同步到Git仓库,再推送到Gitee。这个方案的自动化程度最高,但门槛也最高——你需要运维配合,服务器上要装Git环境、配置SSH key,还要处理同步失败的重试、冲突停止、日志告警等问题。
更麻烦的是,自动同步通常只解决了SVN到Gitee的单向同步,Gitee上的修改要自动反哺回SVN则很难做好,因为两边都在改,冲突处理靠脚本根本处理不明白。我的建议是,自动同步适合那些“SVN是唯一主源、Gitee只读镜像”的场景;如果两边都是活跃开发源,就别指望全自动化,人工摆渡反而更稳。
2.4 三方案对比小结
| 方案 | 维护成本 | 适用场景 | 风险点 |
|---|---|---|---|
| 同目录双仓库 | 低,个人即可 | 团队并行使用两套系统 | 忽略规则配置不当导致元数据混乱 |
| git-svn桥接 | 中 | 个人临时用Git操作SVN | 历史线性约束强,多人协作易冲突 |
| 服务器脚本自动同步 | 高,需运维配合 | SVN单主源、Gitee只读镜像 | 双向同步困难,失败重试麻烦 |
3. 环境准备与基础配置,先把不打架的底子打好
3.1 环境安装和仓库初始化
前端开发机上一般已经装了TortoiseSVN和TortoiseGit,这块没什么特殊要求。重点说初始化顺序:无论已有SVN检出目录,还是想先在Gitee建仓库,标准做法都是先让SVN作为“主工作区”,在SVN检出目录里执行git init。千万不要反过来在Git仓库里做SVN checkout,否则Git的元数据会被SVN的检出动作影响,而且嵌套之后两个图形客户端都会把对方的状态扫描出来,简直是一场灾难。
仓库建好之后的核心动作是设置远端地址。用HTTPS的方式推送Gitee每次都输账号密码,我强烈建议配置SSH key。在Git Bash里执行ssh-keygen -t ed25519 -C "你的邮箱",然后把~/.ssh/id_ed25519.pub内容粘到Gitee个人设置里的SSH公钥页面。之后git remote add origin git@gitee.com:团队名/项目名.git就一劳永逸了。
3.2 两套忽略规则必须对称设置
这是整个方案里最容易被忽略、也最容易出大事故的一步。SVN用svn:ignore属性做忽略,Git用.gitignore文件做忽略,两者互相不认识。如果只设置了一边,另一边的工具就会把对方该忽略的东西全追上。
SVN的忽略设置用命令做最干净。在项目根目录执行svn propedit svn:ignore .,在弹出的编辑器里写入:
node_modules dist .git然后执行svn propset svn:ignore --recursive把属性递归应用到子目录(不同版本写法有差异,也可以直接在TortoiseSVN的"Add to ignore list"里右键完成)。注意.git目录如果不加进SVN忽略,一旦有人用TortoiseSVN的“添加”按钮不小心把整个.git文件夹提交进去,那提交量会直接爆炸。
Git的忽略规则同样要面面俱到,在.gitignore里写入:
.svn/ node_modules/ dist/ coverage/ *.log .env.local .DS_Store这里我额外说明一点:node_modules和dist看着像常识,但在双仓库场景下的危害比单仓库大得多。SVN提交 node_modules 会拉高服务器存储、拖慢所有同事的svn update,而Git提交 node_modules 会把数万个文件写进对象库,Gitee的仓库大小会失控。我在实际操作中见过同事把整个 node_modules 提交到SVN,结果那次svn update跑了快半小时,全组人一起骂娘。
3.3 元数据目录管理:别把.svn和.git提交到对方仓库
踩过这个坑的人应该不少:在Git仓库里执行git add -A,眼睛一花,把.svn目录也加进来了。结果就是每次SVN更新文件,Git都会检测到大量变化,因为.svn目录里的内部文件也在变,整个Git历史里全是垃圾提交。
一旦出现这种情况,不要慌,但处理速度要快。先用git rm -r --cached .svn把.svn从Git索引里移除,然后把.svn写进.gitignore,再提交一次清理掉历史里的引用。反过来也一样,如果发现SVN要提交.git了,先在SVN里执行删除,再设置svn:ignore属性。
我自己的判断标准很简单:任何以点开头的目录,默认不进版本库,除非项目真正需要它(比如.npmrc、.env.example这种配置文件)。宁可多忽略一个真实文件,也不要放进来一堆元数据炸弹。
4. 双向同步实操手册
4.1 从SVN检出并初始化Git仓库
假设SVN仓库地址是http://192.168.1.100/svn/frontend-project,完整初始化步骤:
# 第一步:从SVN检出主分支代码 svn checkout http://192.168.1.100/svn/frontend-project/trunk frontend-project # 第二步:进入目录,初始化Git cd frontend-project git init # 第三步:设置Gitee远程仓库 git remote add origin git@gitee.com:yourteam/frontend-project.git # 第四步:先创建一份.gitignore,把.svn和构建产物排除掉 echo -e ".svn/\nnode_modules/\ndist/" > .gitignore # 第五步:把当前SVN工作区内容作为Git的首次提交 git add -A git commit -m "chore: 初始化Git仓库,镜像SVN当前代码" git push -u origin main这一步要特别注意:SVN检出的文件里如果有换行符是CRLF,Git首次提交时会在工作区自动转换或保留原样,这会影响后续的diff表现。建议在git config core.autocrlf true(Windows环境)或git config core.autocrlf input(Mac/Linux环境)先定下来,避免后面每次同步都出现全文件变动的假象。
4.2 正向同步:SVN的提交推到Gitee
这是最常见的操作路径,开发者在SVN里提交了自己的代码,然后要把最新代码同步到Gitee。我的标准流程是先拉SVN的更新,再推Git:
# 1. 先保证本地与SVN服务器同步 svn update # 2. 解决冲突并确认SVN工作区是干净的 svn status # 3. 提交或更新SVN上所有改动 svn commit -m "feat: 完成登录页重构" # 4. 拉取Gitee远端最新代码(大多数时候没人动,但保险起见) git pull --rebase origin main # 5. 把SVN产生的所有改动变成Git提交 git add -A git commit -m "feat: 完成登录页重构" # 6. 推送到Gitee git push origin main如果git pull卡住,常见原因是Gitee上有别人推的代码而本地也有未提交改动。我的处理方式是先git stash暂存本地改动,pull完再git stash pop,如果有冲突就在工作区里手工解决。记住一个原则:所有冲突都要在SVN工作区解决干净之后再进行Git的提交,不要让Git提交带着冲突标记,不然代码看板上全是乱码。
4.3 反向同步:Gitee上的变更合并回SVN
反向同步的核心变化是“Gitee不是主源”,所以步骤要更谨慎。正常流程:
# 1. 先从Gitee把最新改动拉到本地 git pull --rebase origin main # 2. 确保本地没有未提交的SVN改动 svn status # 3. 先提交SVN上暂时未提交的本地改动(如有) svn commit -m "chore: 本地改动提交" # 4. 把Git工作区与SVN工作区的差异合并 # 此时Git pull改动的文件已经落在工作区,SVN会识别为修改 svn add --force . svn commit -m "feat: 合并Gitee上提交的改动" # 5. 最后再推一次Git,确保Gitee上的记录也最新 git push origin main这里最容易出问题的是svn add --force .。Git pull 下来的新文件对SVN来说是“未版本控制”的,直接svn commit不会带上它们,必须先把新文件svn add进去。用svn add --force .可以把所有新文件一次性加进来,它跟svn add的区别是遇到已版本控制的文件不会报错,正好适合这种批量操作场景。
4.4 同步命令速查表
| 操作 | SVN命令 | Git命令 |
|---|---|---|
| 拉取最新代码 | svn update | git pull --rebase origin main |
| 查看状态 | svn status | git status |
| 添加新文件 | svn add 文件名 | git add -A |
| 提交 | svn commit -m "说明" | git commit -m "说明" |
| 推送 | 无需推送,commit即提交 | git push origin main |
| 批量添加未版本控制文件 | svn add --force . | 同上git add -A |
组合记忆其实很简单:SVN里 commit 就是一步到位,Git里 commit 还要 push 才到远端。我们团队日常总结成一句话:“先SVN后Git,先更新后提交”,只要按照这个顺序操作,基本不会出乱子。
5. 常见问题与排查实录
5.1 覆盖图标消失,怎么判断文件状态
用TortoiseSVN的时候,每个文件左下角都有绿色对勾或红色感叹号。同时装TortoiseGit之后,你会发现有些文件图标被吞了,一会显示SVN的图标,一会显示Git的,甚至什么都不显示。这是两个Shell扩展在抢Explorer的图标槽位。
这不是文件出问题了,只是显示优先级冲突。我的处理方式是把TortoiseGit的图标设置改成“仅当前文件夹”,在TortoiseGit Settings的Icon Overlays里调整,给SVN让出更多表现空间。更省事的办法是命令行看状态,SVN用svn status,Git用git status,不受Shell扩展影响,永远准确。
5.2 忽略规则漏配置,仓库被垃圾文件灌满
这个问题我在3.2里提到过,但实际遇到的情景更恼人。有一次同事在Gitee上看到一个组件目录里出现了.svn/wc.db这个文件,而SVN服务器上出现了dist/整个构建产物目录。两边仓库都被对方管理体系里的垃圾灌了一部分。
排查思路是这样的:先看哪边的仓库出现了不该出现的目录,然后当场删掉并提交清理。真正要反思的不是删,而是为什么会漏。SVN的svn:ignore属性是“目录级”的,不是全局配置,新人检出代码后在新目录里跑npm install,生成的 node_modules 如果没被递归忽略照样会被提交。Git的.gitignore是文件,跟着仓库走的,相对可控。所以我的经验是:尽量把忽略规则写成Git仓库内的.gitignore文件统一维护,同时在SVN服务器根目录设置全局忽略清单,覆盖所有项目。
5.3 中文文件名和行尾符引发的幽灵差异
前端项目里中文文件名太常见了,比如首页.vue、用户模块.js。SVN和Git对这些文件名的编码处理不同,有时候Git仓库里看到的是首页.vue,SVN里显示是转义后的%E9%A6%96%E9%A1%B5.vue。这会导致一个问题:Git diff 时中文文件名的变化被误判为“删除+新增”,代码评审里显得特别乱。
解决方案是配置文件层面统一:Git Bash里设置git config --global core.quotepath false,让Git直接显示中文文件名不转义。行尾符问题更隐蔽,Windows下编辑过的文件全是CRLF,Git第一次提交后如果core.autocrlf没配好,每次git status都显示一堆文件有改动,实际上只是换行符变了。配好core.autocrlf true之后,Git在提交时把CRLF转成LF,检出时再转回CRLF,SVN那头保持不变,两边就都安静了。
5.4 分支与标签的同步策略
双仓库场景下,分支管理是最容易失控的地方。SVN支持trunk、branches、tags三个目录,而Git用git branch管理分支。两者不是一对一映射关系,强行同步会把自己绕晕。
我的实践经验是:Gitee仓库只维护一个main主分支作为SVN trunk的镜像,所有开发分支都只在SVN上管理,不往Gitee推。需要做RFC或者评审的功能,在本地Git仓库建个feature分支,合并回main后推Gitee,但SVN上只保留trunk一条线。这样就能避免两边分支网络互相污染。标签也一样,SVN的tags目录是独立的版本快照,Gitee上不打tag,只依赖仓库的commit节点回溯。这件事最好写进团队规范里,因为人是很容易在分支操作上“自由发挥”的。
6. 团队协作规范和个人经验
6.1 明确同步方向与责任人
双仓库方案里,同步方向一旦模糊,就会出现“谁都不推、谁都在推”的局面。我们在团队里定了几条硬规矩:SVN是公司正式代码源,所有需求合入必须走SVN的trunk;Gitee是展示和评审平台,由前端组长或每次迭代的负责人执行“SVN到Gitee”的同步,其他人不在Gitee上直接改代码;涉及到MR合入Gitee的变更,必须由专人负责拉回SVN并提交。
这几条规矩的执行效果很明显,最直观的就是冲突变少了。之前大家自由同步的时候,同一天内SVN和Gitee各改一个文件,第二天两边的代码都对不上号,排查半天发现是同步顺序错了。有了明确责任人,整个流程固定下来,出问题就知道找谁。
6.2 提交信息与代码评审约定
两套系统的提交信息最好保持统一。SVN的提交信息格式用feat: 说明、fix: 说明、chore: 说明,Git的提交信息直接沿用SVN的写法,不要两边各写一套。这样做的好处是追溯代码时,在Gitee的commit历史里能看到和SVN提交记录一一对应的说明,两边diff也能对上。
代码评审这块,我的建议是评审在Gitee上看diff、留评论,但合入口径是“评审通过后,代码先去SVN部,再同步回Gitee”。不要反过来,先在Gitee合MR再往SVN回传,因为Gitee上的分支合并动作容易把commit历史搞复杂,回传后SVN的线性历史会被破坏。
6.3 我踩过的坑和现成脚本
最后分享两个我踩过最深的坑。第一次是初始化Git时没把node_modules排除,本地git add -A把几万个文件全部提交进了Git对象库,Gitee仓库直接拉到十个G以上,清理的时候只能重做仓库。第二次是同事在SVN上修改了一个文件名,Git这边同步后出现了“删除旧文件+新增新文件”的奇怪状态,代码评审时评审人看不出来是改名,只看到一个文件没了、一个文件多了,最后只能靠提交信息“猜”业务意图。
现在我们把同步操作封装成一个脚本,放在项目根目录,每次执行一条命令就能完成整套同步流程:
#!/usr/bin/env bash # 正向同步:SVN -> Gitee svn update || exit 1 svn status echo "检查上面状态,确认没有未解决冲突" read -p "按回车继续..." svn commit -m "$1" || exit 1 git pull --rebase origin main || exit 1 git add -A git commit -m "$1" || exit 1 git push origin main || exit 1脚本里的read -p是故意加的,强制你在提交前确认一遍SVN状态,防止带着冲突直接同步。自动化是好事,但双仓库场景里还是保留一个人工确认的环节最安全。
关于“前端项目同时使用SVN和Gitee管理代码”这件事,我的总体感受是:这确实是一种临时状态,但它能让你在现有公司基础设施不动摇的前提下,把代码展示、外部协作和新工具链逐步接起来。如果你也正被这个需求困扰,先从同目录双仓库做起,把忽略规则和同步顺序理清楚,再让团队跟着规范走,基本就能稳下来。后面真的觉得同步太繁琐、想要彻底摆脱SVN的时候,你已经有了一套完整的Git仓库和Gitee远程仓库,迁移的底气也攒足了。