简介:Mercurial和Git是两种流行的分布式版本控制系统,各有优势。由于Git在社区支持和功能生态上的领先地位,许多项目需要从Mercurial迁移到Git。本文详细介绍了如何使用fast-export工具完成这一迁移过程,涵盖环境准备、仓库导出、历史记录保留及远程推送等关键步骤。通过本指南,开发者可高效、完整地实现版本库转换,确保代码历史与协作信息无损迁移,为后续基于Git的开发协作奠定基础。
Mercurial 与 Git 迁移实战:从理论到落地的全链路解析
在现代软件工程中,版本控制系统早已不再是“用不用”的问题,而是“怎么用得更好”的命题。我们每天都在提交、合并、分支、回滚,而这一切的背后,是 Git 和 Mercurial 这两个分布式版本控制系统的长期角力。
你可能已经习惯了 git push 的顺滑,但有没有想过,为什么有些老项目还固守着 .hg 目录?为什么某些金融系统宁可放弃 GitHub 的生态,也要坚持使用 hg ?又或者,当你接到一个任务:“把这 10 年历史的 Mercurial 仓库迁到 Git”,你会从哪下手?
🤔 别慌——这不是一次简单的“导出再导入”,而是一场涉及 数据完整性、团队协作惯性、CI/CD 流水线重构 的系统级迁移工程。
今天,我们就来一次彻头彻尾的深挖:从底层原理讲起,手把手带你完成一场高保真、可审计、支持增量同步的 Mercurial → Git 迁移实战。
准备好了吗?🚀
分布式之争:Git vs Mercurial 的设计哲学差异
先别急着敲命令,咱们得搞清楚一个问题: 为什么会有迁移需求?
答案很简单: 生态决定命运。
GitHub 的崛起让 Git 成为事实标准,Bitbucket 对 Mercurial 支持的逐步收缩,加上 CI/CD 工具链几乎全线拥抱 Git,导致很多原本稳定的 Mercurial 项目不得不面对“不迁就死”的局面。
但这并不意味着 Mercurial 不优秀。相反,它的设计更偏向“企业友好”:
- ✅ 操作语义清晰,学习曲线平缓;
- ✅ changegroup 打包机制更适合审计追踪;
- ✅ 内置权限模型比 Git 更精细;
- ❌ 社区生态弱,插件少,自动化工具链落后。
而 Git 呢?它走的是另一条路:
- ✅ 极致灵活,一切皆对象(blob/tree/commit);
- ✅ 分支轻量,合并强大;
- ✅ 全球最大开发者社区加持;
- ❌ 底层复杂,初学者容易误操作。
数据模型对比:快照 vs 变更集
| 特性 | Git | Mercurial |
|---|---|---|
| 存储模型 | 文件快照(Snapshot) | 变更集(Changegroup) |
| 提交标识 | SHA-1 hash | Node ID (SHA-1) |
| 分支实现 | Refs ( refs/heads/* ) | 命名 head + branch map |
| 标签存储 | Refs 或 annotated tags | .hgtags 文件记录 |
举个例子,当你执行 git commit ,Git 实际上会:
1. 计算工作区所有文件的 SHA-1;
2. 构建一棵 tree 对象;
3. 创建一个 commit 对象指向该 tree,并记录父提交。
而 Mercurial 是按“变更”打包的,每次提交生成一个 changegroup 包,包含文件差异、元数据等信息。
所以你看,它们根本就是两种思维模式:
💡 Git 关注“状态” ——我现在的代码长什么样?
💡 Mercurial 关注“动作” ——你改了哪些东西?
这也决定了迁移不能简单粗暴地“复制粘贴”,必须做一次精准的“语义翻译”。
# 看看 Git 是如何组织对象的
git cat-file -p HEAD
输出可能是这样的:
tree abc123...
parent def456...
author Alice <alice@company.com> 1700000000 +0800
committer Bob <bob@company.com> 1700000000 +0800
Initial commit
每个字段都对应着 Git 内部的对象结构。我们的目标,就是在迁移后,让每一个 Mercurial 的变更集都能还原成这样一条完全一致的提交记录。
hg-fast-export:跨系统迁移的桥梁
说到迁移,市面上有不少方案:手动导出、脚本拼接、第三方服务……但真正能扛住生产环境考验的,还得是 hg-fast-export 。
这个由社区维护的开源工具,堪称目前最高效的 Mercurial → Git 转换器。它不是简单的包装器,而是一个基于 Git fast-import 协议 的流式转换引擎。
它到底强在哪?
- ⚡️ 高性能:绕过常规
git add/commit,直接写入对象数据库; - 🧩 高保真:完整保留作者、时间、分支、标签、合并关系;
- 🔁 可重复:支持增量同步,适合长期演进的活跃仓库;
- 🛠️ 可配置:自定义作者映射、分支规则、编码处理……
一句话总结: 它让你感觉不到“迁移”的存在。
工作流程图解
graph LR
A[Mercurial Repository] -->|读取变更集| B(hg-fast-export.py)
B -->|生成Git事件流| C{stdout管道}
C -->|输入流| D[git fast-import]
D -->|写入对象| E[Git Object Database]
是不是有点像编译器?前端解析源码(hg),中间生成 IR(fast-import 流),后端生成目标代码(Git objects)。整个过程无需临时文件,内存占用低,速度快得飞起!
典型的调用方式如下:
hg-fast-export.sh -r /path/to/hg/repo | git fast-import
这里的关键在于 | 管道符——它把 hg-fast-export 的输出实时喂给 git fast-import ,形成一个“生产者-消费者”模型,效率拉满。
参数说明速查表
| 参数 | 作用 | 是否常用 |
|---|---|---|
-r | 指定源 Mercurial 仓库路径 | ✅ 必填 |
--force | 强制覆盖已有引用 | ⚠️ 谨慎使用 |
--branches | 指定要导出的分支列表 | ✅ 控制范围 |
--tags | 是否包含标签 | ✅ 默认开启 |
--authors FILE | 指定作者映射文件 | ✅ 推荐设置 |
--notes refs/notes/hg | 将原始 nodeid 写入 notes | ✅ 审计必备 |
📌 小贴士:
--notes功能非常实用!迁移完成后,你可以通过git log --notes=refs/notes/hg查看每条提交对应的 Mercurial nodeid,方便后续比对和溯源。
如何安装 hg-fast-export?三步搞定!
由于 hg-fast-export 并未收录进主流包管理器(pip/apt/yum),我们需要手动部署。
第一步:获取源码
推荐使用 Git 克隆官方仓库(由 frej/fast-export 维护):
git clone https://github.com/frej/fast-export.git /opt/hg-fast-export
cd /opt/hg-fast-export
ls -l
你应该能看到这些关键文件:
-
hg-fast-export.sh—— 主入口脚本 -
hg-fast-export.py—— 核心 Python 逻辑 -
hg-reset.py—— 辅助重置工具 -
README.md—— 使用文档
当然,你也可以选择下载 zip 包:
wget https://github.com/frej/fast-export/archive/refs/heads/master.zip
unzip master.zip -d /tmp/
mv /tmp/fast-export-master /opt/hg-fast-export
建议统一放在 /opt 或 /usr/local/libexec 下,便于多项目共享。
🔐 安全提醒 :始终检查代码来源!查看提交历史、issue 数量、PR 活跃度,必要时审查 hg-fast-export.py 内容,确认无恶意逻辑。
第二步:安装运行依赖
hg-fast-export.py 是用 Python 写的,但它依赖 Mercurial 的内部 API 来读取仓库数据,所以必须安装 Mercurial 的 Python 绑定。
支持环境要求
| 组件 | 最低版本 | 推荐版本 |
|---|---|---|
| Python | 2.7 或 3.6+ | 3.8+ |
| Mercurial | 3.0+ | 5.9+ |
| Git | 1.7.8+ | 2.30+ |
不同系统的安装命令如下:
Ubuntu/Debian:
sudo apt update
sudo apt install python3 python3-pip mercurial git-core
CentOS/RHEL:
sudo yum install python3 python3-pip mercurial git
# 或使用 dnf(较新版本)
sudo dnf install python3 mercurial git
macOS(Homebrew):
brew install python@3.9 hg git
安装完记得验证版本:
python3 --version
hg --version
git --version
然后测试能否导入 Mercurial 模块:
python3 -c "from mercurial import ui, localrepo; print('✅ OK')"
如果报错 No module named 'mercurial' ,那就用 pip 补装:
pip3 install mercurial
⚠️ 注意:部分发行版(如 Ubuntu)的 python3-hglib 包可能不完整,建议优先通过系统包管理器安装 mercurial ,而不是 pip。
第三步:配置环境变量与执行权限
为了让 hg-fast-export.sh 在任意目录下都能调用,建议将其加入 $PATH 。
echo 'export PATH="/opt/hg-fast-export:$PATH"' >> ~/.bashrc
source ~/.bashrc
接着赋予脚本可执行权限:
chmod +x /opt/hg-fast-export/*.sh
chmod +x /opt/hg-fast-export/*.py
如果你希望所有用户都能使用,可以创建软链接到 /usr/local/bin :
sudo ln -s /opt/hg-fast-export/hg-fast-export.sh /usr/local/bin/hg-fast-export
sudo ln -s /opt/hg-fast-export/hg-reset.py /usr/local/bin/hg-reset
现在就可以全局调用了:
hg-fast-export --help
🎉 搞定!工具链已准备就绪。
工具链自检:迁移前的最后一道防线
别急着跑迁移脚本!先做一轮完整的工具链验证,避免中途失败。
1. 测试帮助命令是否正常
python3 /opt/hg-fast-export/hg-fast-export.py --help
预期输出应包含类似内容:
usage: hg-fast-export.py [-h] [-r REPO] [--rev REV] [-A AUTHORS] ...
Convert Mercurial repository to Git via fast-import.
如果出现语法错误或模块缺失,请回头检查 Python 版本与 Mercurial 绑定。
2. 检查 hg 和 git 是否可用
which hg
which git
hg version
git version
确保这两个命令都能正常调用。否则迁移过程中会因找不到二进制而中断。
3. 模拟一次空仓库导出
mkdir /tmp/test-migration && cd /tmp/test-migration
git init
/opt/hg-fast-export/hg-fast-export.sh -r /path/to/an/hg/repo
即使路径无效,也应该看到合理的错误提示(比如 “repo not found”),而不是脚本崩溃。这说明控制流已经进入主程序。
4. 常见问题排查表
| 错误现象 | 可能原因 | 解决方法 |
|---|---|---|
ImportError: No module named mercurial | 缺少 Mercurial Python 库 | apt install mercurial 或 pip3 install mercurial |
SyntaxError: invalid syntax (Python 2 报错) | 使用了 Python 2 | 改用 python3 执行 |
hg: command not found | hg 未安装或不在 PATH | 安装 hg 并添加路径 |
git fast-import failed | Git 版本过低 | 升级至 1.7.8+ |
Permission denied on .git | 权限不足 | chmod 或 sudo 调整 |
| 中文乱码或 UnicodeDecodeError | 编码未设为 UTF-8 | 设置 export PYTHONIOENCODING=utf-8 |
建议启用调试模式查看详细日志:
hg-fast-export.sh -r /path/to/hg/repo --debug | tee export.log
只有当所有组件协同无误时,才能开始正式迁移。
获取并初始化 Mercurial 仓库:打好迁移地基
工欲善其事,必先利其器。迁移的第一步,是从远程拉取一份完整的 Mercurial 副本。
克隆远程仓库(带流式优化)
mkdir -p ~/migration-project/hg-source
cd ~/migration-project/hg-source
hg clone --stream https://hg.company.com/scm/main-app ./
📌 --stream 是重点!对于大仓来说,它可以显著提升克隆速度,因为它跳过了传统的逐个解析变更集的过程,直接传输压缩后的变更流。
其他常用参数:
| 参数 | 说明 |
|---|---|
-r REV | 只克隆特定分支或版本 |
--pull | 强制从源端拉取数据 |
--insecure | 忽略 SSL 证书验证(仅限测试) |
建议在 SSD 上操作,并开启进度显示:
# ~/.hgrc
[ui]
progress = True
处理认证问题:SSH vs HTTPS
大多数企业仓库都需要身份验证。
SSH 密钥配置(推荐用于自动化)
ssh-keygen -t rsa -b 4096 -C "migration-bot@company.com"
保存为 ~/.ssh/id_rsa_hg_migration ,然后将公钥添加到 Bitbucket/HGWeb 的用户设置中。
配置 SSH 别名简化连接:
# ~/.ssh/config
Host hg.company.com
HostName hg.company.com
User hg
Port 2222
IdentityFile ~/.ssh/id_rsa_hg_migration
IdentitiesOnly yes
测试连接:
ssh hg@hg.company.com info
HTTPS 凭证管理
不推荐明文写密码:
hg clone https://username:password@hg.company.com/repo # ❌ 危险!
建议使用 Mercurial 的 keyring 插件:
[extensions]
mercurial_keyring =
[auth]
company.prefix = https://hg.company.com
company.username = migration-user
首次克隆时会提示输入密码,并自动加密存储。
| 方式 | 安全性 | 自动化友好度 | 推荐场景 |
|---|---|---|---|
| SSH 密钥 | 高 | 高 | CI/CD、长期维护 |
| HTTPS + Keyring | 中高 | 中 | 交互式迁移 |
| 明文 URL | 低 | 高 | 临时测试(记得清 history) |
校验本地副本完整性
克隆完成后,必须验证是否与源完全一致。
# 查看最近几条提交
hg log -l 5 --template "{rev}:{node|short} {author|person} {date|isodate} {desc|firstline}\n"
# 统计总提交数
hg log --template '.' | wc -c
# 查看所有分支
hg branches -a
# 查看标签
hg tags
理想情况下,你还应该从源服务器导出一份权威的变更集摘要进行哈希比对:
# 在源端
hg log --template "{node}\n" > /tmp/hg-revlist.txt
# 在本地
hg log --template "{node}\n" | sort > ~/migration-checksums/local-revs.txt
diff /tmp/hg-revlist.txt ~/migration-checksums/local-revs.txt
零差异才是可信源。✅
graph TD
A[开始克隆远程hg仓库] --> B{克隆成功?}
B -- 否 --> C[检查网络/认证配置]
C --> D[修正SSH或HTTPS设置]
D --> B
B -- 是 --> E[运行hg log统计提交数]
E --> F[与源统计数据比对]
F --> G{数量匹配?}
G -- 否 --> H[重新克隆或增量同步]
H --> E
G -- 是 --> I[检查分支与标签完整性]
I --> J{全部存在?}
J -- 否 --> K[补充拉取缺失分支]
J -- 是 --> M[标记本地副本为“可信源”]
设计迁移工作目录结构:整洁才有生产力
别小看目录规划,混乱的结构会让你在后期排查问题时抓狂。
推荐结构:
~/migration-project/
├── hg-source/ # 原始 hg 克隆
├── git-bare/ # 目标 Git 裸仓库
├── temp/ # 中间缓存
├── logs/ # 日志输出
├── scripts/ # 自定义脚本
└── backups/ # 备份归档
一键创建:
mkdir -p ~/migration-project/{hg-source,git-bare,temp,logs,scripts,backups}
好处多多:
- 隔离性强,不怕误删;
- 日志集中,便于审计;
- 脚本能复用,提高效率。
记得提前备份原 hg 仓库:
tar -czf ~/migration-project/backups/hg-source-backup-$(date +%Y%m%d_%H%M%S).tar.gz \
-C ~/migration-project/hg-source .
初始化 Git 目标环境:打造接收容器
迁移的目标通常是裸仓库(bare repo),因为它不含工作区,专用于接收外部提交流。
git init --bare ~/migration-project/git-bare/main-app.git
标准结构如下:
main-app.git/
├── objects/
├── refs/
├── config
├── HEAD
└── description
别忘了设置基础信息:
cd ~/migration-project/git-bare/main-app.git
git config user.name "Migration Automation Script"
git config user.email "migration-bot@company.com"
git config init.defaultBranch main # 使用 main 而非 master
还要配置 .gitattributes 防止跨平台问题:
cat > info/attributes << 'EOF'
* text=auto
*.sh text eol=lf executable
*.py text eol=lf executable
*.png binary
*.jpg binary
*.zip binary
EOF
这样能保证 Windows/Mac/Linux 开发者协作时不因换行符炸掉 diff。
执行历史导出:真正的重头戏来了!
终于到了核心步骤。
./hg-fast-export.sh \
-r ../hg-source \
--git-repo ../git-bare/main-app.git \
--authors authors.map \
--max-commit-batch 500 \
--incremental \
--notes refs/notes/hg
关键参数解释:
-
--max-commit-batch 500:控制内存,防 OOM; -
--incremental:支持断点续传; -
--authors:规范化作者邮箱; -
--notes:保留原始 nodeid 用于审计。
导出过程中你会看到进度条:
Exporting revision 12456/89231 (14.0%) [added: 345, modified: 1289]
可以用 tee 记录日志:
./hg-fast-export.sh ... | tee logs/export.log
万一中断了怎么办?不用担心, --incremental 会自动从中断点继续。
原理是读取 .git/hg-map 文件中的最后处理节点:
def load_last_rev(git_repo):
try:
with open(f"{git_repo}/.git/hg-map", "r") as f:
lines = f.readlines()
if lines:
return int(lines[-1].split()[0])
except FileNotFoundError:
return -1
聪明吧?🧠
作者 & 时间 & 编码:细节决定成败
作者信息映射
Mercurial 允许 "John Doe" 这种格式,但 Git 推荐 <john@company.com> 。
解决办法: authors.map
John Doe = John Doe <john.doe@company.com>
jane = Jane Smith <jane.smith@company.com>
配合脚本自动生成初始映射:
import subprocess
def extract_authors(repo_path):
result = subprocess.run(
['hg', 'log', '--template', '{author}\n'],
cwd=repo_path,
capture_output=True,
text=True
)
authors = set(result.stdout.strip().split('\n'))
with open('authors.map', 'w') as f:
for a in sorted(authors):
email = a.replace(' ', '.').lower() + '@company.com'
f.write(f"{a} = {a} <{email}>\n")
flowchart LR
A[扫描hg log] --> B[去重清洗]
B --> C[生成候选邮箱]
C --> D[输出map文件]
D --> E[人工审核]
E --> F[投入迁移]
时间戳精度
Mercurial 是秒级,Git 是纳秒级。虽然有差距,但 hg-fast-export 会尽量保留原始时间与时区。
不要轻易加 --nodates ,那等于抹掉了历史真相。
编码兼容性
务必设置:
export PYTHONIOENCODING=utf-8
export LANG=en_US.UTF-8
并在 .hgrc 中声明:
[ui]
encoding = UTF-8
遇到非法路径字符(如 : 、 * ),可用 --filename-sanitize 自动替换。
迁移后验证:闭环才算完成
检查提交数量是否一致
hg log --template '.' | wc -c
git log --oneline | wc -l
检查分支结构
hg branches -a
git branch -r
图形化对比提交图谱
git log --graph --oneline --all
或使用 gitg / tig 可视化工具。
推送到远程 Git 服务器
git remote add origin https://gitlab.example.com/team/project.git
git push origin --all
git push origin --tags
记得设置保护分支策略,防止直接 push 到 main。
常见问题与解决方案
| 问题 | 解法 |
|---|---|
| 子仓库未迁移 | 手动迁移 subrepo 并转为 git submodule |
| 空提交过多 | git rebase -i --root 删除 |
| CI/CD 不兼容 | 更新 Jenkinsfile/GitLab CI 中的 checkout 方式 |
| 权限丢失 | 映射 Mercurial 组 → GitLab 角色 |
建议设立 1–2 周双轨期,旧系统只读,新系统写入,平稳过渡。
flowchart TD
A[Migrate Main Repo] --> B[Push to Git Server]
B --> C{Subrepos Exist?}
C -->|Yes| D[Migrate Each Subrepo]
D --> E[Add as Git Submodule]
C -->|No| F[Verify History]
F --> G[Update CI/CD Pipelines]
G --> H[Adjust Team Permissions]
H --> I[Announce Migration Complete]
这场迁移,不只是工具的更换,更是协作范式的升级。当你看到第一个 PR 在 GitLab 上被 approve 时,你会明白:所有的努力,都值得。💪
简介:Mercurial和Git是两种流行的分布式版本控制系统,各有优势。由于Git在社区支持和功能生态上的领先地位,许多项目需要从Mercurial迁移到Git。本文详细介绍了如何使用fast-export工具完成这一迁移过程,涵盖环境准备、仓库导出、历史记录保留及远程推送等关键步骤。通过本指南,开发者可高效、完整地实现版本库转换,确保代码历史与协作信息无损迁移,为后续基于Git的开发协作奠定基础。
8069

被折叠的 条评论
为什么被折叠?



