在开发WordPress插件External Media without Import的时候,因为希望插件能在WordPress官方渠道发布,所以需要在WordPress官方提供的svn远程仓库上托管这个项目。WordPress官方给我提供的svn仓库的地址是https://plugins.svn.wordpress.org/external-media-without-import/。
但另一方面,考虑到github作为开源社区的人气,以及fork、pull request等代码贡献的便利性,我也想在github上托管这个项目。因此我在github上也创建了一个仓库:https://github.com/zzxiang/external-media-without-import.git。
与此同时,我希望本地只需要维护一个项目文件夹,或者绝大部分操作只需要在一个文件夹中执行。这个文件夹由git管理,并可以方便地与WordPress的svn和github双方同步。不过后来经过一段时间摸索,我似乎只能做到让git和svn仓库的trunk分支同步,但这也足够了。
也就是说,我希望实现的应用场景如下图所示:
WordPress官方提供的svn仓库和github上的仓库在刚创建好的时候都是空库,里面没有任何文件;而在此之前我已经在本地的git管理的文件夹中工作了一段时间,里面已经有多条提交记录和多份文件。我希望将这些历史记录和文件都同步到远程的svn和git仓库。最后要达成的效果是远程svn仓库的trunk分支、远程git仓库的master分支和本地git仓库的master分支中的提交一一对应。这三个分支保持同步,完全相同。
与github的同步较为简单,毕竟本地和远程都是git仓库。因此先来解决和svn仓库同步的问题。我要将本地的git仓库和远程的svn仓库之间关联起来。
出于示例的目的,我在本地从零开始建立一个git仓库和一个svn仓库,并用它们模拟这种情况,让git仓库已有的提交全部同步到svn仓库,并且在此之后能直接向svn仓库提交以及直接从svn仓库更新。实际应用的时候将下文以 file:// 为前缀的svn仓库URL替换为实际的 svn:// 或 https:// 为前缀的URL即可。
先在本地创建一个git仓库,并往里面添加一些提交:
mkdir local-git-repos
cd local-git-repos/
git init
touch test.txt
git add test.txt
git commit -am "Added test.txt."
echo aaaa >> test.txt
git commit -am "Added aaaa."
echo bbbb >> test.txt
git commit -am "Added bbbb."
现在git仓库里应该有一个text.txt并且已经有三条提交:
$ ls
test.txt
$ cat test.txt
aaaa
bbbb
$ git log
commit 15db9719b735c65b4d8ffc2cbf08b54ebad09e38
Author: Zhixiang Zhu <zzxiang21cn@hotmail.com>
Date: Wed Jul 5 20:36:36 2017 +0800
Added bbbb.
commit e1d6c1556fa210ca0a0b12e661ac5d23e5d215d2
Author: Zhixiang Zhu <zzxiang21cn@hotmail.com>
Date: Wed Jul 5 20:36:35 2017 +0800
Added aaaa.
commit d769c9a48932782a683336d478356ccd124077cd
Author: Zhixiang Zhu <zzxiang21cn@hotmail.com>
Date: Wed Jul 5 20:36:35 2017 +0800
Added test.txt.
接着创建一个svn仓库:
cd ..
svnadmin create svn-repos
用svn客户端在仓库里创建好trunk、branches和tags这样的标准目录:
svn co file://`pwd`/svn-repos svn-client
cd svn-client/
mkdir trunk branches tags
svn add *
svn ci -m "Added trunk, branches and tags."
接下来可以开始将git和svn关联起来,从现在起基本上所有操作都全部在git中执行,如果不需要开辟svn分支和标签的话,甚至可以将svn-client目录删掉。
进入git仓库,先用 git svn init 命令将svn仓库的地址添加到git目录的配置中:
cd ../local-git-repos/
git svn init --stdlayout file://`pwd`/../svn-repos/ --prefix=svn/
本地git目录里的.git/config文件中应该增加了如下一段配置:
[svn-remote "svn"]
url = file:///Users/zhixiangzhu/git-svn-test/local-git-repos/../svn-repos
fetch = trunk:refs/remotes/svn/trunk
branches = branches/*:refs/remotes/svn/*
tags = tags/*:refs/remotes/svn/tags/*
在日常工作中,一般是通过 git svn rebase 将svn仓库里的提交更新到本地的git仓库,并且将本地git仓库中还未同步到远程的提交rebase到已从远程仓库更新下来的提交之上。但现在就执行这条命令的话会报错:
$ git svn rebase
Unable to determine upstream SVN information from working tree history
git svn info 命令也会报同样的错误。这是因为git仓库中尚不存在远程svn仓库的对应分支。我们要先通过fetch命令获取svn仓库的内容,从而创建出这个远程分支。
$ git svn fetch -rHEAD
r1 = 72572673ab962bd61cd3f244f8cf63beca1bc8f8 (refs/remotes/svn/trunk)
实际应用中我曾遇到过fetch无效的现象,似乎是因为svn仓库中没有提交也没有文件。用svn客户端往仓库里提交一份文件(内容任意,注意不是文件夹)后,git这边就能fetch到东西了。
现在本地git目录中应该多了个远程分支对应svn仓库:
$ git branch -a
* master
remotes/svn/trunk
此时也可以通过git日志看到本地git目录中svn分支的存在:
$ git log --oneline --decorate --all --graph
* d9b5878 (svn/trunk) Added trunk, branches and tags.
* 15db971 (HEAD -> master) Added bbbb.
* e1d6c15 Added aaaa.
* d769c9a Added test.txt.
现在将git仓库中已有的提交全部rebase到svn远程分支上:
$ git rebase --onto remotes/svn/trunk --root master
First, rewinding head to replay your work on top of it...
Applying: Added test.txt.
Applying: Added aaaa.
Applying: Added bbbb.
现在 git svn rebase 和 git svn info 应该能正常工作了:
$ git svn rebase
Current branch master is up to date.
$ git svn info
Use of uninitialized value $lc_author in concatenation (.) or string at /usr/local/Cellar/git/2.8.1/libexec/git-core/git-svn line 1639.
Use of uninitialized value $lc_rev in concatenation (.) or string at /usr/local/Cellar/git/2.8.1/libexec/git-core/git-svn line 1640.
Path: .
URL: file:///Users/zhixiangzhu/git-svn-test/local-git-repos/../svn-repos/trunk
Repository Root: file:///Users/zhixiangzhu/git-svn-test/local-git-repos/../svn-repos
Repository UUID: 79d69224-e83d-4033-9427-31fe05b277a8
Revision: 1
Node Kind: directory
Schedule: normal
Last Changed Author:
Last Changed Rev:
Last Changed Date: 2017-07-06 14:22:31 +0800 (四, 06 7 2017)
上面 git svn info 的输出中出现 Use of uninitialized value $lc_author 的提示是因为 Last Changed Author 和 Last Changed Rev 为空,从svn客户端中也看不到提交记录(其实我对这一点不是很明白,因为明明应该有一个创建trunk、branches和tags目录的提交):
$ svn log
------------------------------------------------------------------------
将git仓库中的提交推送到svn仓库中,这个错误消息就会消失:
$ git svn dcommit
Committing to file:///Users/zhixiangzhu/git-svn-test/local-git-repos/../svn-repos/trunk ...
A test.txt
Committed r2
A test.txt
r2 = 89de67ea07dc796a5ce4b96bb4786a890eab86d7 (refs/remotes/svn/trunk)
M test.txt
Committed r3
M test.txt
r3 = 1aeeb3dcc30e0b4c03e27981ff04dce11472151e (refs/remotes/svn/trunk)
M test.txt
Committed r4
M test.txt
r4 = 22a31f42be3a51c285b4fa07b3ef4e53bcf07625 (refs/remotes/svn/trunk)
No changes between aa2560cf0de937d4045bc2f30dcab46752d65b94 and refs/remotes/svn/trunk
Resetting to the latest refs/remotes/svn/trunk
$ git svn info
Path: .
URL: file:///Users/zhixiangzhu/git-svn-test/local-git-repos/../svn-repos/trunk
Repository Root: file:///Users/zhixiangzhu/git-svn-test/local-git-repos/../svn-repos
Repository UUID: 79d69224-e83d-4033-9427-31fe05b277a8
Revision: 4
Node Kind: directory
Schedule: normal
Last Changed Author: zhixiangzhu
Last Changed Rev: 4
Last Changed Date: 2017-07-06 20:37:07 +0800 (四, 06 7 2017)
现在我们已经将本地git仓库和svn仓库成功关联起来。接下来要给本地git仓库添加远程git仓库的引用。这个工作比较简单,在本地git仓库目录中执行以下命令:
git remote add origin https://github.com/zzxiang/external-media-without-import.git
.git/config文件中应该会增加下面一段内容:
[remote "origin"]
url = https://github.com/zzxiang/external-media-without-import.git
fetch = +refs/heads/*:refs/remotes/origin/*
最后把本地git仓库中的提交全部push到远程git仓库:
git push --set-upstream origin master
- 本地git仓库向svn仓库推送提交: git svn dcommit
- 本地git仓库从svn仓库获取更新: git svn rebase
- 本地git仓库向远程git仓库推送提交: git push
- 本地git仓库从远程git仓库获取更新: git pull
一般不需要再使用svn客户端。唯一的例外是需要在svn仓库中创建分支和标签的时候。虽然git也有 git svn branch 和 git svn tag 命令可用于该目的,但在我的环境中这两个命令却会报错:
$ git svn tag 1.0
Copying https://plugins.svn.wordpress.org/external-media-without-import/trunk at r1669373 to https://plugins.svn.wordpress.org/external-media-without-import/tags/1.0...
Authentication failed: No more credentials or we tried too many times.
Authentication failed at /usr/local/Cellar/git/2.8.1/libexec/git-core/git-svn line 1196.
本文参考了这篇文章:Using Git and Subversion Together。
本文在我的独立博客上的地址:http://zxtechart.com/2017/07/16/use-two-remote-repos-of-svn-and-git-at-the-same-time-for-the-same-project/