VS Code 一个项目连两个 Git 仓库?双远程推送配置完全指南
用过 VS Code 做开发的朋友十有八九会遇到这种情况同一个项目既想推到 GitHub 上做个开源备份或者给团队协作又想推到公司内部的 GitLab或者自建的 Gitea做私有归档甚至还有可能推到云效、CODING 之类的平台。以前我都是来回改remote origin地址或者干脆复制两份目录管理起来一团糟。后来把一个项目同时关联到两个 Git 仓库这件事彻底搞明白了才发现其实非常简单核心就靠git remote的几个命令和 VS Code 里的小配置。这篇就专门聊聊在 VS Code 的同一个项目里如何优雅地连接两个 Git 仓库并且推送代码时能手动指定推送到其中某一个仓库。不管你是前端、后端还是运维只要天天跟 Git 打交道这篇内容都能直接用到实际工作里。1. 为什么要在一个项目里连两个远程仓库1.1 双远程仓库的真实应用场景先说需求从哪来。我以前接过一个外包项目代码要同时交付给两个不同的合作方。甲方 A 要求代码统一推到他们的内网 GitLab甲方 B 则要求代码必须同步到他们指定的云效仓库两边都是 必须推。如果只配一个origin每次交付前都要改配置、检查远程地址、确认有没有推错精神高度紧张。后来在开源社区维护一个小工具库也遇到了类似的需求主仓库放在 GitHub但国内访问不稳定很多同事从 Gitee 拉代码更顺畅。我就把 Gitee 配成了第二个远程仓库两边同步推谁拉都方便。还有一类高频场景是备份。本地项目对你自己很重要推 GitHub 算公开可能不合适推公司 GitLab 又担心离职后仓库被回收所以再单独配一个私有仓库做冷备。多一个远程仓库等于多一份安全感。1.2 VS Code 的 Git 集成到底能做多少VS Code 自带的源代码管理面板快捷键CtrlShiftG已经封装了日常 90% 的 Git 操作包括暂存、提交、拉取、推送、查看 diff 等。但它默认只展示和使用一个名为origin的远程仓库。很多人误以为 VS Code 不支持多远程仓库其实不是不支持是默认的推送按钮只会推送到当前分支的上游分支。在终端里执行git remote -v就能看到你的项目其实可以关联任意多个远程地址。VS Code 之所以让你觉得 不灵活是因为它的图形界面没有把每个远程仓库的推送操作单独做成按钮但可以通过配置 push 的目标或者直接切换到终端操作。这个我们后面细说。提示VS Code 图形界面的推送按钮等价于在终端执行git push。它会推送到你当前分支的上游分支并不会自动推送到所有远程仓库。理解这一点你就不会被 推送失败 或 推错仓库 的问题困住了。1.3 两个主流方案选哪个我梳理了一下目前解决 一个项目连两个仓库 这个问题业界主流做法有两种第一种是双远程仓库名方案。也就是同一个项目里同时添加origin和backup或任意命名两个远程仓库推送时用git push backup main来指定推送到其中一个。这是 Git 原生支持的玩法灵活度最高完全可控。适合两个仓库内容不完全相同、或者你希望在推送时明确区分场景的情况。第二种是同一远程多 URL方案。通过git remote set-url --add给同一个远程仓库添加多个推送地址执行git push时 Git 会依次推送到所有配置的 URL。这种方式适合一次推送多个平台同步的场景比如一份代码要同时推 GitHub 和 Gitee。我的建议是如果你需要的是推送时指定其中一个仓库用第一种方案如果你需要的是推送时一次性分发到多个平台用第二种方案。这篇会重点讲第一种因为它的适用范围更广也更容易理解底层原理。2. 动手操作给项目添加第二个远程仓库2.1 准备工作先检查当前项目的远程配置不管你是刚克隆下来的项目还是本地已经开发了很久的老项目第一步永远是先看清现状。打开 VS Code 的终端Ctrl执行git remote -v正常情况下你会看到类似这样的输出origin https://github.com/yourname/yourproject.git (fetch) origin https://github.com/yourname/yourproject.git (push)这表示当前项目只关联了一个远程仓库名字叫origin。如果这个命令报错fatal: not a git repository说明当前目录还没初始化 Git先在终端执行git init再说。我还遇到过这种情况有些项目是从 SVN 转过来的或者从压缩包解压的里面虽然有一堆代码但根本没有.git目录。这时候git remote -v同样会提示不是一个 Git 仓库那就需要先初始化然后git add .、git commit -m init把项目纳入 Git 管理。2.2 添加第二个仓库git remote add 命令详解确认现状之后添加第二个远程仓库的命令非常简单git remote add backup https://gitlab.com/yourname/yourproject.git这里我把第二个远程仓库命名为backup你也可以叫gitee、mirror、company名字随意只要你自己能分清就行。命令执行后没有任何输出就是成功再执行一次git remote -vorigin https://github.com/yourname/yourproject.git (fetch) origin https://github.com/yourname/yourproject.git (push) backup https://gitlab.com/yourname/yourproject.git (fetch) backup https://gitlab.com/yourname/yourproject.git (push)这样项目里就有了两个远程仓库。如果哪天想移除第二个仓库执行git remote remove backup即可。想重命名执行git remote rename backup archive也行。有人会问第二个远程仓库需要先建好吗我的经验是最好先去代码托管平台GitHub、Gitee、GitLab新建一个空仓库拿到 HTTPS 或 SSH 地址然后回到本地添加。空仓库带不带 README 都无所谓但建议连 README 也不要初始化免得推送时出现历史冲突。你的本地项目已经有一堆提交记录了远程仓库如果是空的推送会非常顺滑。2.3 推送到第二个仓库git push 指定远程仓库配置好之后推送就分两种情况了。如果你想把代码推送到原来的仓库也就是origin什么都不用改直接推送即可git push origin main如果你想把代码推送到第二个仓库命令也很直观git push backup main这里backup是你添加远程仓库时起的名字main是分支名。有些老项目分支名还是master建议先执行git branch看一下当前分支名别想当然。如果你不想每次推送都带分支名第一次推送时可以指定上游分支git push -u backup main-u是--set-upstream的简写意思是把本地main分支的上游分支设置为backup/main。之后你再执行git pushGit 会默认推送到backup而不是origin。这里有个细节很多人会忽略当你对backup使用了-u之后VS Code 源代码管理面板上的推送按钮推的就是backup不是origin。如果你一会儿推这个、一会儿推那个建议还是老老实实带远程仓库名不要过度依赖默认推送。我们后面会再讲这个坑。2.4 VS Code 图形界面如何指定推送仓库VS Code 源代码管理面板虽然默认只显示一个推送按钮但并不是完全没有办法在图形界面操作多远程仓库。方法一在源代码管理面板点击右上角的更多操作三个点的图标在下拉菜单里能看到推送和推送到...。点击推送到...VS Code 会弹出远程仓库列表让你手动选择推送到哪个远程仓库还能选择远程分支名。方法二在命令面板CtrlShiftP里输入Git: Push to...和上面效果一样也是直接远程仓库列表。方法三如果完全不记得命令也可以直接打开项目里的.git/config文件自己改远程仓库配置。我先不展开这个内容后面有专门一节讲配置文件的结构。实际使用下来我推荐图形界面的Git: Push to...给不熟终端的朋友用操作直观、不会打错命令。但如果你需要推送的频率高还是记住git push backup main这个命令更快。毕竟熟能生巧命令敲多了一次成型。3. 两个仓库内容要保持一致试试同一远程多 URL 方案3.1 给 origin 设置多个 push 地址前面讲的是两个远程仓库名方案接下来讲同一远程多 URL方案。这个方案适合那些希望每次git push自动推到多个平台的场景比如你要维护一个开源项目同时同步到 GitHub 和 Gitee。先看当前远程仓库配置git remote -v然后给origin添加第二个推送地址git remote set-url --add origin https://gitee.com/yourname/yourproject.git这里的命令格式是git remote set-url --add 远程仓库名 新地址。执行后再看git remote -v你会发现origin变成了三条记录origin https://github.com/yourname/yourproject.git (fetch) origin https://github.com/yourname/yourproject.git (push) origin https://gitee.com/yourname/yourproject.git (push)注意fetch只有一个地址push有两个地址。这表示拉取的时候只从 GitHub 拉推送的时候会同时推送到 GitHub 和 Gitee。这时候你正常执行git pushGit 会尝试推送两次先推 GitHub再推 Gitee。如果其中一个推送失败Git 会报错停止另一个成功与否以输出信息为准。这个机制适合两边内容完全一样的情况省去了每次推两个仓库的麻烦。3.2 去掉某个 push 地址和恢复默认推送如果你的需求变了不想让git push再自动推到那个多余的仓库可以执行git remote set-url --delete origin https://gitee.com/yourname/yourproject.git这个命令会对origin的 push 地址列表做减法删掉指定地址后git push又变回只推送到默认的 GitHub 地址。如果只是想查看当前远程地址和 push URL 的对应关系用git remote -v git remote get-url origin --push第二个命令用来查看某个远程仓库的 push URL。它默认显示第一个 push 地址如果配了多个会全部打印出来。3.3 两种方案的权衡和选择建议我把这两种方案的优缺点整理成了一张表大家按实际需求选择。对比项双远程仓库名方案同一远程多 URL 方案核心命令git remote add backup urlgit remote set-url --add origin url推送方式git push backup maingit push自动推多个地址是否可指定推送单个非常灵活随便指定不灵活一次推全部是否可分别拉取可以git fetch backup不可以fetch 只有一个地址适用场景内容不完全一致、需要定向推送内容完全一致、多平台自动同步配置复杂度低低我的选择标准很简单如果两个仓库的用途不同比如一个是团队主仓库一个是个人备份内容不完全一致用双远程仓库名方案如果两个仓库只是平台不同、内容完全镜像用同一远程多 URL 方案。不过说句实在话我真正在日常工作中用得最多的还是双远程仓库名方案。因为推送时能指定推送到其中一个仓库这个需求本质上就需要保留一个非默认的选择权给每个仓库独立的身份才是正道。4. 深入理解 .git/config双仓库的底层配置长什么样4.1 从配置文件理解 Git 的远程仓库机制很多人学了git remote一堆命令但还是不知道这些配置到底存在哪儿。其实所有远程仓库信息都记录在项目根目录的.git/config文件里。这个文件是一个纯文本 INI 格式的文件用 VS Code 打开就能直接看。一个关联了双远程仓库的.git/config大概是这样的[core] repositoryformatversion 0 filemode true bare false logallrefupdates true [remote origin] url https://github.com/yourname/yourproject.git fetch refs/heads/*:refs/remotes/origin/* [remote backup] url https://gitlab.com/yourname/yourproject.git fetch refs/heads/*:refs/remotes/backup/* [branch main] remote origin merge refs/heads/main重点看[remote origin]和[remote backup]两个小节。每个远程仓库就是一个小节小节名就是远程仓库名小节里配置了url仓库地址和fetch拉取规则。执行git remote add时本质上就是在config文件里新增一个[remote xxx]小节。[branch main]这一段记录的是本地main分支的上游分支。remote origin表示本地main分支的上游在origin仓库merge refs/heads/main表示上游分支是origin仓库里的main分支。这个配置决定了git push不写明远程仓库时的默认目标。如果你执行过git push -u backup main这里的[branch main]小节的remote会变成backup也就是默认推送目标变了。理解了这个文件的机制你就能明白很多问题为什么会出现。比如两个远程仓库都叫main分支当你git fetch全量拉取的时候本地的refs/remotes/origin/main和refs/remotes/backup/main会分别更新互不干扰这也是 Git 能同时支持多远程仓库的根本原因。远程跟踪分支本身是隔离的每个远程仓库有独立的命名空间。4.2 手工修改配置文件会踩什么坑虽然 Git 官方不推荐你手工改.git/config但实际工作中很多人包括我都干过这件事。我这里说几个手工改配置时最需要注意的地方。第一是格式必须严格对齐。每个[remote xxx]小节的节名必须处于独立行节内选项用key value的格式两侧可以留空格但不能用 Tab 代替。如果格式错了Git 后续操作会直接报错报错信息往往很不友好。第二是 URL 别写错尤其是 HTTPS 的地址。别把https://写成http://也别多一个空格。有些对安全比较敏感的平台访问时还要配 SSH keyURL 要写成gitgithub.com:user/repo.git这种 SSH 格式。写错了会导致权限认证失败。第三是改完配置后最好在 VS Code 的源代码管理面板里重新加载窗口CtrlShiftP输入Developer: Reload Window让 VS Code 的 Git 扩展重新读取配置文件。很多时候改了.git/config但界面没刷新就会造成明明改了却不生效的假象。第四是设置文件权限。在部分不安全的共享环境里Git 会检查.git/config的权限如果发现对所有人可写会直接拒绝操作并提示你修复权限。在 Linux 或 macOS 上可以执行chmod 600 .git/config把配置文件改成仅当前用户可读写问题就解决了。4.3 配置文件中默认推送行为的调整技巧[remote origin]小节里可以加一个仓库级别的 push refspec来调整默认推送行为。这属于进阶技巧如果你只是想要指定推送仓库其实用不上但了解了对理解 Git 很有帮助。默认情况下直接执行git push origin main等价于推送本地main分支到远程origin/main分支。如果你想修改这个默认行为可以在.git/config的[remote origin]中添加push refs/heads/main:refs/heads/main意思是把本地main分支推送到远程main分支。如果远程分支名和本地不一样写成push refs/heads/main:refs/heads/master这样git push origin时Git 会默认把本地main推到远程master。这种技巧在旧项目迁移时偶尔会用到但日常双仓库需求完全不用关心它。我在这儿提一嘴是希望大家看到类似配置时不至于一脸懵。5. 实战录VS Code 里连两个仓库完整走一遍5.1 从零配置到成功推送的完整流程理论讲再多不如完整走一遍。我模拟一个最常见的场景本地已经有一个 Vue 项目现在要同时连 GitHub 和 Gitee并且要求推送时能指定平台。第一步打开 VS Code进入项目目录按 Ctrl 打开终端。执行git init git add . git commit -m first commit如果是新项目这个流程能保证本地有一个干净的历史记录。如果是从远程克隆的项目这一步可以跳过。第二步添加第一个远程仓库。假设 GitHub 仓库已经建好地址是https://github.com/devxiaoming/awesome-project.git执行git remote add origin https://github.com/devxiaoming/awesome-project.git第三步添加第二个远程仓库。假设 Gitee 仓库地址是https://gitee.com/devxiaoming/awesome-project.git执行git remote add gitee https://gitee.com/devxiaoming/awesome-project.git第四步验证配置git remote -v确认两条远程仓库都正常显示。第五步分别推送。先推 GitHubgit push -u origin main再推 Giteegit push -u gitee main推完后两个平台上都能看到代码了。之后如果你想推 GitHub就执行git push origin main想推 Gitee就执行git push gitee main。完全可控。5.2 VS Code 图形界面操作时的真实表现接下来说说 VS Code 图形界面的表现。完成上面的配置后回到源代码管理面板你会看到分支名旁边通常会显示上游信息。如果你最后执行的是git push -u gitee main那么 VS Code 面板上的推送按钮默认推送的目标就是 Gitee。在面板里修改提交信息、点击提交、点击推送整个推送过程会在输出面板里显示 Git 命令的真实执行记录。我习惯每次推送后都扫一眼输出面板确认推送的目标仓库到底对不对。如果我想临时推送另一个仓库我会打开命令面板输入Git: Push to...回车后 VS Code 会弹出一个菜单列出你所有远程仓库对应的目标分支比如origin maingitee main这时候选择gitee main或者origin main就可以定向推送了。这个操作我实测了很多次非常稳定。提示VS Code 的Git: Push to...实际上是在帮你拼git push remote branch命令所以它要求本地和远端分支都有明确的名字。如果远程仓库提示非 fast-forward 推送被拒可以先拉取远端代码再推。5.3 同时推两边的 一次性同步 操作有时候你做完一次大改动就是想两边仓库都同步一遍。虽然可以敲两条 push 命令但如果你希望更偷懒还有两个办法。办法一配置同一远程多 URL前面讲过的set-url --add方案之后正常git push就会同时推多个平台。适合长期镜像同步。办法二保持双远程仓库名不变但用一个 shell 别名偷懒。在~/.bashrc或~/.zshrc里加一行alias push-allgit push origin main git push gitee main然后每次执行push-all就自动依次推送到两个仓库。不过用连接有个问题如果第一个仓库推送失败第二个仓库不会被推送。你可以改成分号;但分号的问题是即使第一个失败第二个也会执行有时候你根本不知道第一个失败了。综合考虑我还是更推荐用因为推送失败通常意味着代码问题或权限问题先解决了再推第二个更安全。这里我得提醒一点在团队协作的项目里同时推多个仓库时要小心 hooks。如果你配置了 CI/CD 流水线推送 GitHub 和 Gitee 会分别触发不同的流水线如果流水线内容不一样可能会造成重复构建或错误部署。我在实际项目中就遇到过Gitee 配置了自动部署到测试服GitHub 配置了镜像同步结果两边同时推时测试服被反复触发。后来改成只在主仓库触发自动部署另一个仓库关闭了流水线钩子才消停下来。5.4 使用 SSH 方式连接远程仓库的配置补充前面用的都是 HTTPS 地址这种方式每次推送时都可能要求输入用户名和密码虽然可以通过凭据管理器记住但在某些终端环境里还是很烦。如果你习惯用 SSH配置双远程仓库时要把地址写成 SSH 格式。比如 GitHub 的 SSH 地址是gitgithub.com:devxiaoming/awesome-project.gitGitee 的 SSH 地址是gitgitee.com:devxiaoming/awesome-project.git。先把本机公钥添加到两个平台的后台然后执行git remote add origin gitgithub.com:devxiaoming/awesome-project.git git remote add gitee gitgitee.com:devxiaoming/awesome-project.git推送时git push -u origin main git push -u gitee mainSSH 方式的好处是不用反复输密码而且连接更稳定。但要注意每个平台都要把公钥加一次漏了就会提示权限拒绝。想测试 SSH 连接是否正常GitHub 可以执行ssh -T gitgithub.comGitee 可以执行ssh -T gitgitee.com。我的建议是如果你的两台远程主机都是团队内部使用频繁的优先配 SSH 免密体验会好很多。如果只是偶尔同步归档用 HTTPS 也无妨。6. 常见问题与避坑指南6.1 推送被拒绝或认证失败怎么办最经典的报错是remote: Permission to xxx.git denied to xxx.或者fatal: unable to access https://github.com/xxx/xxx.git/: The requested URL returned error: 403遇到这类问题先检查 URL 是不是写错了。执行git remote -v看清所有远程地址然后确认当前登录的账号是否有该仓库的推送权限。如果是公司内网的 GitLab还需要确认你的账号是不是已经加入了相关项目组。还有一次我遇到一个很隐蔽的问题某台电脑上配置了全局 Git 用户信息但和远程仓库所属账号不是同一个邮箱。GitLab 会把提交邮箱和账号绑定邮箱不匹配时推送代码虽然能成功但你在网页上看到的提交人是个幽灵用户没有头像。这不是严格意义的失败但会影响团队协作审计。解决办法是执行git config --global user.name yourname git config --global user.email your_emailexample.com如果只想改当前项目去掉--global即可。6.2 远程仓库历史不一致导致的冲突双仓库如果历史不一致推送时会遇到 force update 被拒绝的情况。常见的报错是! [rejected] main - main (fetch first) error: failed to push some refs to ...意思是你本地和远程的分支没有共同祖先或者远程分支上有你本地没有的提交。这种问题通常出现在你往一个已有提交记录的远程仓库推代码时。比如远程仓库初始化的时候带了 README而本地仓库是基于空仓库提交的两条线没有任何交集。解决办法有几种稳妥方案先执行git pull origin main --rebase把远程提交拉下来基于远程提交之上重放你的本地提交然后重新推送。如果远程仓库内容很少比如只有一个 READMErebase 一般不会冲突。强制覆盖方案执行git push origin main --force或git push -f。这个命令会直接用本地历史覆盖远程历史非常危险只适合确认远程内容是垃圾、不需要保留时使用。如果两个仓库的内容都重要建议先手动处理冲突把两边代码合并成一个统一历史再推送到所有平台。对于同时维护多远程仓库的用户我个人的习惯是任何非必要的快速修复都不要用--force真的推错了冷静处理比强行覆盖靠谱。6.3 VS Code 推送按钮不生效或推错仓库VS Code 推送按钮默认推送当前分支的上游分支这个逻辑前面已经强调过。如果你遇到明明点了推送但远端仓库没更新的情况大概率是分支的上游配置不对。排查方法在终端执行git branch -vv输出结果里每一行前面的[origin/main]或[gitee/main]就标识了本地分支的上游。比如* main 1234567 [origin/main] fix: update readme[origin/main]表示本地main分支的上游在origin仓库。这时候在 VS Code 点推送推送的就是origin。如果你想改默认上游执行git branch --set-upstream-togitee/main main之后 VS Code 的推送按钮就默认推送到gitee了。这种即时可控的操作也是 Git 设计里比较顺手的地方。6.4 删除本地的远程跟踪分支和远程仓库清理如果git branch -a里出现一堆remotes/origin/xxx和remotes/backup/xxx但某些分支你已经不用了可以用git remote prune origin或git fetch --prune清理过期的远程跟踪分支。不过在日常双仓库场景里这个操作很少用到。比较常见的是某个远程仓库地址变了你需要修改origin的地址。比如 GitHub 仓库从devxiaoming迁到了neworg可以执行git remote set-url origin https://github.com/neworg/awesome-project.git再次执行git remote -v确认地址已经更新。这个命令只改地址不会影响已有的分支跟踪关系。如果你确定不再需要某个远程仓库执行git remote remove backup这会移除backup仓库及其所有的远程跟踪分支只影响本地 Git 配置不会删除远程平台上的仓库所以可以放心操作。6.5 一个容易忽视的安全提醒最后啰嗦一句安全相关的内容。配置双远程仓库时不要把包含敏感信息的仓库推送到公共平台。代码、配置文件、密钥文件在提交前要检查清楚尤其注意.env、*.pem、*.key等文件。我习惯在项目根目录维护一套严格的.gitignore把密钥、日志、依赖目录、本地配置文件都排除在版本控制之外。还有一点如果你发现自己不小心把不该公开的仓库推到了公开平台第一时间把远程仓库设为私有同时尽快轮换相关密钥和凭据不要有侥幸心理。7. 实操心得总结这套双远程仓库的配置方法我在多个项目里反复用过最深的体会是Git 的远程仓库本质就是一套名称到 URL的映射理解了这一点想连几个仓库都随意关键是要有清晰的命名规范和推送习惯。我个人的习惯是主维护仓库统一叫origin其他辅助仓库按平台或用途命名比如gitee、backup、archive。每次推送前我会习惯性地在终端敲一下git remote -v确认当前配置无误再操作。尤其是同时维护多个项目非常容易记混仓库地址这一步能规避大量低级错误。还有一个小心得是把git push origin main和git push gitee main这类命令写进个人笔记或项目 README 里方便自己回看也方便新同事接手。毕竟双仓库配置是项目初始化时一次性的事情时间久了容易忘。写清楚之后后人接手项目时能少走很多弯路。最后再分享一个日常使用的小技巧如果你只是想临时看某个远程仓库的分支列表不需要完整拉取可以执行git ls-remote origin或git ls-remote backup。这个命令只访问远程仓库并返回引用信息不修改本地状态用来确认远程分支是否存在非常方便。我在切换远程仓库配置时经常用到一眼就知道地址有没有配对。