Skip to content
hanyong edited this page Dec 10, 2013 · 1 revision

git-svn 使用经验

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 过后才能合并到其他分支.

  1. 一个 svn 版本对应的本地 git 提交不能唯一预先确定, 是在被拉取到本地 git 仓库, 创建本地 git 提交后才能确定. 不同 git-svn 客户端可能从不同的起点版本开始拉取 svn 版本, 同一个 svn 版本在不同 git 结点对应的提交不一样, 导致不同 git 结点看到的是不同的仓库, 无法互相沟通.

  2. git 本地 "提交" 提交到 svn 仓库后, 会被重写添加 svn 元信息. 会被重写的提交历史不能公布出去, 也不允许被其他分支合并. 而提交过后其他客户端可以直接与 svn 服务器通信, 不必与其他 git 结点通信.

拉取 svn 版本

不要直接使用 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

clone git-svn 仓库

新的工作拷贝不必从头从 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: 添加示例输出.