假设你刚刚学会了一套绝世武功(Git),正准备在江湖中大展拳脚。可是当你兴冲冲地来到团队时,却发现大家还在用着传统的“少林功夫”(比如 Subversion)或者“武当剑法”(比如 Perforce)。 你当然希望所有人都能学会这门新武功,但现实往往不允许我们一蹴而就。

这时候,我们通常有两个选择。第一个选择,是做一个“隐世高手”,在不惊动团队现有工作流程的前提下,悄悄地在自己的小天地里施展 Git 的神奇魔法。就像武侠小说里的那些深藏不露的大侠,表面上和大家一样,暗地里却有着独门绝技。 第二个选择,则是说服整个团队一起“改换门庭”,集体迁移到更现代、更强大的 Git 平台上来。
这两种策略,正好对应了我们今天要探讨的两个核心话题:如何让 Git 与其他版本控制系统和平共处,以及如何将一个已有项目完整地迁移到 Git 的世界里。
在所有“传统门派”中,Subversion(简称 SVN)算得上是德高望重的老前辈了。如果你的团队还在使用 SVN,那么 git svn 就是你的“翻译官”,它能够完美地理解两种“语言”之间的差异。
首先,我们需要将 SVN 仓库克隆到本地,转换成一个 Git 仓库。
|# 克隆 SVN 仓库到本地,-s 参数表示使用标准布局 # 这就像是在 SVN 的地盘上建立了一个 Git 的秘密基地 $ git svn clone http://svn.example.com/project/ -s my_project
这个过程可能需要一些时间,因为它要把 SVN 的每一次提交都拉取下来,在本地转换成 Git 的提交记录。完成后,你就拥有了一个功能完整的本地 Git 仓库。
现在,你可以像在任何普通 Git 项目里一样自由地工作了。创建分支、提交代码、修改历史,尽情发挥 Git 的强大功能。
|# 在本地创建一个新分支,就像在秘密基地里开辟新的训练场 $ git checkout -b feature-branch # 修改代码并提交,Git 的暂存区让你可以精雕细琢每一次提交 $ git add . $ git commit -m "添加了用户登录功能" # 如果需要,你还可以使用 rebase 来整理提交历史 $ git rebase -i HEAD~3
当你完成所有工作后,需要将这些改动同步回 SVN 服务器。 首先,拉取服务器上的最新变更并重新应用你的本地提交:
|# 拉取 SVN 服务器的最新变更,并将本地提交重新应用到最新版本之上 # 这就像是先学习最新的少林功夫,然后再把自己的独门绝技嫁接上去 $ git svn rebase
然后,将你的本地 Git 提交转换成 SVN 的版本记录并推送:
|# 将本地的 Git 提交逐一转换成 SVN 的版本记录并推送到服务器 # 这就像是把独门绝技伪装成少林功夫,让其他人都看不出来 $ git svn dcommit
重要提醒:dcommit 命令会在你的 Git 提交信息末尾添加一个 git-svn-id 标记,这会改变你本地提交的 SHA-1 值。
因此,如果你同时还需要与另一个 Git 远程仓库协作,请务必先 dcommit 到 SVN,再 push 到 Git 仓库,否则会引发混乱。
与 SVN 协作时,最好保持历史的线性。因为 SVN 无法理解 Git 的合并提交。如果你在本地合并了两个分支,dcommit 时会将所有变更"压扁"成一个提交,丢失掉宝贵的分支历史。所以,推荐使用 git rebase 而不是 git merge。
如果你的项目使用的是 Mercurial(简称 Hg),那情况就好比遇到了失散多年的兄弟。Mercurial 和 Git 同属分布式版本控制系统,它们的设计理念和工作方式非常相似。git-remote-hg 这个工具能让它们之间的交流变得异常顺畅。
安装好 mercurial 和 git-remote-hg 之后,你就可以像克隆一个 Git 仓库一样克隆 Mercurial 仓库,只需要在 URL 前面加上 hg:: 前缀。
|# 克隆 Mercurial 仓库,就像拜访失散多年的兄弟 $ git clone hg::http://hg.example.com/project/ /tmp/project-git
接下来的工作流程几乎和纯 Git 一样。你可以 fetch、pull、push,甚至可以进行 merge 操作,因为 Mercurial 完全能理解合并是怎么一回事。
|# 获取远程仓库的最新变更 $ git fetch origin # 合并远程分支的变更 $ git merge origin/master # 推送本地变更到远程仓库 $ git push origin master
在许多大型企业中,你可能会遇到像 Perforce 或微软的 Team Foundation Version Control(TFVC)这样的"重量级选手"。面对这些"武林盟主",我们同样有应对之策。
git-p4 就是你在 Perforce 地盘上使用的"游击工具"。设置好 P4PORT 环境变量后,就可以克隆一个 Perforce 仓库。
|# 设置 Perforce 服务器地址 $ export P4PORT=perforce.example.com:1666 # 克隆 Perforce 仓库,@all 表示克隆所有历史记录 # 这就像是在武林盟主的地盘上建立秘密据点 $ git p4 clone //depot/project/main@all my-project
同步服务器的最新变更,使用 git p4 sync。为了保持历史线性,推荐使用 git p4 rebase:
|# 同步服务器的最新变更并重新应用本地提交 $ git p4 rebase
当你准备好提交时,运行 git p4 submit,它会把你本地的 Git 提交逐一转换成 Perforce 的变更集:
|# 将本地 Git 提交转换成 Perforce 变更集并提交 $ git p4 submit
对于微软的 TFVC,git-tfs 是一个功能更强大的选择,尤其因为它支持分支。
|# 克隆 TFVC 仓库,--with-branches 会自动把 TFVC 的分支映射过来 $ git tfs clone --with-branches https://tfs.example.com:443/tfs/DefaultCollection $/MyProject/Main
工作流程类似:git tfs fetch 获取更新,git rebase tfs/default 衍合本地提交,最后使用 git tfs rcheckin 将本地的多个提交逐一推送到 TFVC 服务器。
|# 获取 TFVC 服务器的最新变更 $ git tfs fetch # 将本地提交重新应用到最新版本之上 $ git rebase tfs/default # 将本地提交逐一推送到 TFVC 服务器 $ git tfs rcheckin
当你成功说服团队,大家一致同意“改换门庭”到 Git 这个更现代、更强大的平台时,真正的挑战才刚刚开始。迁移的核心目标,是把过去所有的“武功秘籍”(项目的完整历史记录)一件不漏、完好无损地带过去。
对于像 Subversion、Mercurial 这类主流系统,社区已经为我们准备好了成熟的迁移工具,就像专业的“搬家公司”一样。
这个过程中,有一个至关重要的步骤,那就是整理一份“武林名册”(authors file)。在 SVN 里,作者信息可能只是一个用户名,但在 Git 里需要标准的 "姓名 <邮箱>" 格式。
你可以通过以下命令生成作者列表的模板:
|# 从 SVN 日志中提取所有作者信息并生成模板文件 # 这就像是整理一份武林名册,记录所有参与者的真实身份 $ svn log --xml | grep author | sort -u | \ perl -pe 's/.*>(.*?)<.*/$1 = /' > users.txt
然后手动编辑 users.txt,补全每个用户的姓名和邮箱。接下来,在 git svn clone 时使用这个文件:
|# 使用作者文件克隆 SVN 仓库,--no-metadata 移除 SVN 特有的元数据 # 这就像是把少林功夫完全转换成独门绝技,不留任何痕迹 $ git svn clone http://svn.example.com/project/ \ --authors-file=users.txt --no-metadata -s my_project
--no-metadata 选项会移除 git-svn-id 等元数据,让导入后的仓库看起来更像一个原生的 Git 仓库。
导入完成后,还需要一番“打扫和整理”,把 SVN 的标签和分支转换成地道的 Git 标签和分支:
|# 将 SVN 的标签转换成 Git 标签 # 这就像是把少林寺的牌匾重新制作成独门绝技的招牌 $ cp -Rf .git/refs/remotes/origin/tags/* .git/refs/tags/ $ rm -Rf .git/refs/remotes/origin/tags # 将 SVN 的分支转换成 Git 分支 # 这就像是把少林寺的各个分院重新组织成独门绝技的不同流派 $ cp -Rf .git/refs/remotes/origin/* .git/refs/heads/ $ rm -Rf .git/refs/remotes/origin
最后,添加你新的 Git 服务器地址作为远程仓库,然后把所有内容推送上去:
|# 添加新的 Git 服务器作为远程仓库 $ git remote add origin git@git.example.com:myrepository.git # 推送所有分支到新服务器 $ git push origin --all # 推送所有标签到新服务器 $ git push origin --tags
从 Mercurial 迁移可以使用 hg-fast-export 工具,它同样支持作者文件映射,并将 Mercurial 的历史转换成 Git 能快速导入的格式。
|# 使用 hg-fast-export 将 Mercurial 仓库导出为 Git 格式 $ hg-fast-export -r /path/to/hg/repo --force # 导入到新的 Git 仓库 $ git init new-git-repo $ cd new-git-repo $ git fast-import < ../hg-export
对于 Perforce 和 TFVC,也可以使用 git-p4 和 git-tfs 进行克隆,然后通过 git filter-branch 命令清理掉它们在提交信息中留下的元数据,再推送到新的 Git 服务器。
如果你的项目使用的是某个非常小众,或者年代久远的系统,市面上找不到现成的迁移工具怎么办?别担心,Git 为我们提供了:git fast-import。

你可以把 git fast-import 想象成一套万能的“积木”或“蓝图”,让你能亲手打造一台专属的“时光机器”。
它的工作原理非常直接:你只需编写一个脚本,从你的旧系统中读取所有历史信息,然后按照一种非常简单的文本格式,把项目历史的"故事"讲给 git fast-import 听。
假设你的项目是通过打时间戳的备份目录来管理版本的(back_2024_01_02、back_2024_01_04...)。你可以写一个 Python 脚本来生成“剧本”:
|#!/usr/bin/env python3 import os import time import datetime from pathlib import Path # 设置作者信息 author = "张三 <zhangsan@example.com>" marks = [] def convert_dir_to_mark(directory): """将目录名转换为标记号""" if directory not in marks: marks.append(directory) return str(marks.index(directory) + 1) def
这个脚本会输出这样的“剧本”:
commit refs/heads/master:开始一个新提交mark :1:给这个提交一个唯一标记committer ...:定义作者和时间data ...:定义提交信息from :...:指明上一个提交的标记,将历史串联起来deleteall:清空工作区M 644 inline ...:定义每个文件的模式、路径和内容最后,在一个新的 Git 仓库里,运行这个脚本并将其输出通过管道传递给 git fast-import:
|# 创建新的 Git 仓库 $ git init my_new_repo $ cd my_new_repo # 运行导入脚本并将输出传递给 git fast-import $ python3 import_script.py /path/to/backups | git fast-import
导入完成后,你的文件还不会出现在工作目录。你需要执行 git reset --hard master 来检出最新的文件:
|# 检出最新的文件到工作目录 $ git reset --hard master
无论你身处的环境是新是旧,无论你的团队是拥抱变化还是固守传统,Git 总有办法让你工作得更舒心。 你可以选择成为一名“隐世高手”,在自己的世界里享受 Git 的高效;也可以勇敢地带领团队,完成一次彻底的“改换门庭”。 重要的是,永远不要因为工具的限制而束缚自己的生产力。掌握了这些方法,你就拥有了在不同版本控制系统之间自由穿梭的能力,就像一位真正的武林高手,无论身处何地都能游刃有余。