git-svn 是一个用 git 实现的 svn 客户端. 本地是一个 git 仓库, git-svn 可以与 svn 服务器通信, 将 svn 上的主干和分支转换成本地仓库中的 git 分支, 并能将本地 git 仓库的修改提交到 svn 服务器.
通常我们只关心最近的提交历史和某几个分支, 可以先初始化一个空仓库, 再按需拉取数据.
初始化标准布局的 svn 仓库:
git svn init -s http://svn.apache.org/repos/asf/commons/proper/lang/ commons-lang
没必要 clone 整个仓库, clone 一个 svn 仓库会拉取所有历史和分支, 可能需要花很多时间. 如果 svn 代码管理和提交历史比较混乱可能还会有其他问题. clone 标准布局的 svn 仓库:
git svn clone -s http://svn.apache.org/repos/asf/commons/proper/lang/ commons-lang
git-svn 只是在 git 仓库上添加一些 git-svn 元数据和配置,
可以直接在已有的 git 仓库上执行 git svn init
.
git init commons-lang
cd commons-lang
git svn init -s http://svn.apache.org/repos/asf/commons/proper/lang/
上述 svn 仓库初始化时会看到如下信息:
Using higher level of URL: http://svn.apache.org/repos/asf/commons/proper/lang => http://svn.apache.org/repos/asf
编辑 git 配置文件:
git confgie -e
可看到如下配置段:
[svn-remote "svn"]
url = http://svn.apache.org/repos/asf
fetch = commons/proper/lang/trunk:refs/remotes/trunk
branches = commons/proper/lang/branches/*:refs/remotes/*
tags = commons/proper/lang/tags/*:refs/remotes/tags/*
一个分支对应 svn 上的一个子文件夹, 这段配置描述了 svn 子文件夹和 git 分支的对应关系. "url" 是基本 svn 路径, 分支配置使用相对路径. git-svn 初始化标准 svn 布局的仓库时自动使用仓库根目录作为 url, 这不是必须的, "url" 只要是所有分支目录的公共前缀就可以了.
"fetch" 直接配置了一个子文件夹和一个分支的对应关系.
"branches" 和 "tags" 配置了分支和 tag 的映射关系.
使用 git svn branch
创建的新分支将出现在配置的 "branches" 目录下.
可以包含多个 "fetch" 配置, 将不同的 svn 路径直接映射到 git 分支.
通常一个 git-svn 仓库只能有一个 remote, 即一个 svn remote. 所以 svn 远程分支直接保存到 "remotes" 下, 如 "remotes/trunk", 而不是 "remotes/origin/trunk".
使用 git-svn 协作开发时, 通常所有 git 仓库都应该只与 svn 服务器通信, git 仓库间不能相互通信. 并且 svn 分支的本地 git 提交必须提交到 svn 过后才能合并到其他分支.
-
一个 svn 版本对应的本地 git 提交不能唯一预先确定, 是在被拉取到本地 git 仓库, 创建本地 git 提交后才能确定. 不同 git-svn 客户端可能从不同的起点版本开始拉取 svn 版本, 同一个 svn 版本在不同 git 结点对应的提交不一样, 导致不同 git 结点看到的是不同的仓库, 无法互相沟通.
-
git 本地 "提交" 提交到 svn 仓库后, 会被重写添加 svn 元信息. 会被重写的提交历史不能公布出去, 也不允许被其他分支合并. 而提交过后其他客户端可以直接与 svn 服务器通信, 不必与其他 git 结点通信.
不要直接使用 git svn fetch
,
这样会拉取配置的所有分支,
git-svn 还会尝试查找所有历史,
很麻烦.
通常我们只要在 trunk 上选一个最近的提交版本作为起点版本, 从起点版本开始跟踪 svn 上提交历史. 起点版本后新增的所有分支关系都可以被 git-svn 识别出来, 建立正确的本地 git 分支关系, 使用 git 智能的分支合并功能进行分支合并, 再提交回 svn 仓库.
- 指定 svn 版本号拉取一个版本到本地仓库
git svn fetch -r 100
git-svn 会判断出这个版本对应哪个分支, 在本地仓库创建 git 提交并更新对应的远程分支, 如 "remotes/trunk".
版本号也可以是一个范围:
git svn fetch -r 100:HEAD
- 创建本地分支
与普通 git 仓库相同:
git branch master trunk
- 更新当前分支在 svn 服务器上的最新修改
git svn rebase
这条命令相当于 "svn update
".
- 提交本地修改到 svn 仓库
git svn dcommit
git-svn 根据最近一次提交的 svn 元信息确定要提交到哪个 svn 路径. 分支合并的时候要小心 fast-forward 合并可能导致改变了提交的 svn 路径.
TODO: 添加验证.
- 拉取 svn 上的新分支
svn 上创建分支会产生一个版本号, 即复制分支的第一个版本号, 使用 svn 工具找到这个版本号.
svn log --stop-on-copy http://svn.xx.com/repos/hello/branches/svn-new/ | tail
拉取这个 svn 版本, 类似拉取起点版本:
git svn fetch -r 500
git-svn 会识别创建这个版本对于的新分支, 并识别设置这个分支的 parent 为原有的分支, 正确建立分支间的拓扑结构.
- 重建 git 提交
有时配置错误会影响创建 svn 版本对应的本地提交不对, 需要丢掉错误的 git 提交, 重新拉取 svn 版本.
TODO 验证分支重命名替换导致的问题
可以将 svn 远程分支重置到某个版本, 再拉取重建新 svn 版本.
git svn reset -r 200
git svn rebase
这样只能 reset 当前分支对应的 svn 远程分支.
一次遇到要重建的 "remotes/trunk" 分支引用丢了,
即不能切换过去 reset,
git svn fetch -r
也不能重建远程分支.
然后发现 ".git" 仓库下有个 ".git/svn/refs/remotes/trunk/" 文件夹,
保存了 svn 版本和 git 提交映射关系的源信息,
删掉这个文件夹就可以按拉取新分支的方法重建分支.
rm -rf .git/svn/refs/remotes/trunk/
git svn fetch -r 200
git checkout -b master refs/remotes/trunk
git svn rebase
新的工作拷贝不必从头从 svn 版本建立 git 仓库, 从已建立的仓库 clone 更方便, 还可以更好的保留分支合并关系.
与 clone 普通 git 仓库不同, 两个仓库的远程分支应该是一样的. git-svn man 手册页上有一个示例, 修改 fetch 分支的映射关系, 再初始化 svn 仓库.
git remote add origin server:/pub/project
git config --replace-all remote.origin.fetch '+refs/remotes/*:refs/remotes/*'
git fetch
git checkout -b master FETCH_HEAD
git svn init http://svn.example.com/project
git svn rebase
git-svn 维护了 svn 版本和 git 提交映射关系的元信息, clone git-svn 仓库时不会 clone 这些元信息, 根据提交日志的 svn 元信息和 svn 远程分支映射关系, 就可以重建元信息, 将 git 提交跟 svn 版本关联起来. 还可以手动指定直接 fetch 某个分支.
git init hello2
cd hello2
git remote add origin ../hello
git fetch origin refs/remotes/trunk:refs/remotes/trunk
git check -b master refs/remotes/trunk
git svn init -s http://svn.com/repos/hello/
git svn rebase
TODO: 添加示例输出.