第26章 Git和SVN协同模型
在本篇的最后,将会从另外一个角度来看版本库协同。不是不同的用户在使用Git版本库时如何协同,也不是一个项目包含多个Git版本库时如何协同,而是当版本控制系统不是Git(如Subversion)时,如何能够继续以Git的方式进行操作。
Subversion会在商业软件开发中占有一席之地,因为依然会有公司需要严格而复杂的源代码授权。对于熟悉了Git的用户,一定会对Subversion的那种一旦脱离网络和服务器便寸步难行的工作模式厌烦透顶。实际上对Subversion的集中式版本控制的不满和改进在Git诞生之前就发生了,这就是SVK[1]。
在2003年(Git诞生的前两年),台湾的高嘉良就开发了SVK,用分布式版本控制的方法操作SVN。其设计思想非常朴素,既然SVN的用户可以看到有访问权限数据的全部历史,那么也应该能够依据历史重建一个本地的SVN版本库,这样很多SVN操作都可以通过本地的SVN进行,从而脱离网络。当对本地版本库的修改感到满意后,通过本地SVN版本和服务器SVN版本库之间的双向同步,将改动归并到服务器上。这种工作方式真的非常酷。
不必为SVK的文档缺乏及不再维护而感到惋惜,因为有更强的工具登场了,这就是git-svn。git-svn是Git软件包的一部分,用Perl语言开发。它的工作原理是:
将Subversion版本库在本地转换为一个Git库。
转换可以基于Subversion的某个目录,或者基于某个分支,或者整个Subversion代码库的所有分支和里程碑。
远程的Subversion版本库可以和本地的Git双向同步。Git本地库修改推送到远程Subversion版本库,反之亦然。
git-svn作为Git软件包的一部分,当Git从源码包进行安装时会默认安装,提供git svn命令。而几乎所有的Linux发行版都将git-svn作为一个独立的软件单独发布,因此需要单独安装。例如Debian和Ubuntu运行下面的命令安装git-svn。
$sudo aptitude install git-svn
将git-svn独立安装是因为git-svn软件包有着特殊的依赖,即依赖Subversion的Perl语言绑定接口,Debian/Ubuntu上由libsvn-perl软件包提供。
当git-svn正确安装后,就可以使用git svn命令了。但如果在执行git svn—version时遇到下面的错误,则说明Subversion的Perl语言绑定没有正确安装。
$git svn—version
Can't locate loadable object for module SVN:_Core in@INC(@INC contains:
/usr/share/perl/5.10.1/etc/perl/usr/local/lib/perl/5.10.1
/usr/local/share/perl/5.10.1/usr/lib/perl5/usr/share/perl5
/usr/lib/perl/5.10/usr/share/perl/5.10/usr/local/lib/site_perl
/usr/local/lib/perl/5.10.0/usr/local/share/perl/5.10.0.)at
/usr/lib/perl5/SVN/Base.pm line 59
BEGIN failed—compilation aborted at/usr/lib/perl5/SVN/Core.pm line 5.
Compilation failed in require at/usr/lib/git-core/git-svn line 41.
遇到上面的情况,需要检查本机是否正确安装了Subversion及Subversion的Perl语言绑定。
为了便于对git-svn的介绍和演示,要有一个Subversion版本库,并且要有提交权限以便演示如何用Git向Subversion进行提交。下面就在本地创建一个Subversion版本库。
$svnadmin create/path/to/svn/repos/demo
$svn co file:///path/to/svn/repos/demo svndemo
取出版本0
$cd svndemo
$mkdir trunk tags branches
$svn add*
A branches
A tags
A trunk
$svn ci-m "initialized."
增加branches
增加tags
增加trunk
提交后的版本为1。
再向Subversion开发主线trunk中添加些数据。
$echo hello>trunk/README
$svn add trunk/README
A trunk/README
$svn ci-m "hello"
增加trunk/README
传输文件数据.
提交后的版本为2。
建立分支:
26.1 使用git-svn的一般流程
Successfully followed parent
r3=1adcd5526976fe2a796d932ff92d6c41b7eedcc4(refs/remotes/demo-1.0)
Found possible branch point:file:///path/to/svn/repos/demo/trunk=>
file:///path/to/svn/repos/demo/tags/v1.0,2
Found branch parent:(refs/remotes/tags/v1.0)
1863f91b45def159a3ed2c4c4c9428c25213f956
Following parent with do_switch
Successfully followed parent
r4=c12aa40c494b495a846e73ab5a3c787ca1ad81e9(refs/remotes/tags/v1.0)
Checked out HEAD:
file:///path/to/svn/repos/demo/trunk r2
从上面的输出可以看出,当执行了git svn clone之后,在本地工作目录创建了一个Git库(git-svn-demo),并将Subversion的每一个提交都转换为Git库中的提交。进入git-svn-demo目录,看看用git-svn克隆出来的版本库。
$cd git-svn-demo/
$git branch-a
*master
remotes/demo-1.0
remotes/tags/v1.0
remotes/trunk
$git log
commit 1863f91b45def159a3ed2c4c4c9428c25213f956
Author:jiangxin<jiangxin@f79726c4-f016-41bd-acd5-6c9acb7664b2>
Date:Mon Nov 1 05:49:41 2010+0000
hello
git-svn-id:file:///path/to/svn/repos/demo/trunk@2
f79726c4-f016-41bd-acd5-6c9acb7664b2
commit 2c73d657dfc3a1ceca9d465b0b98f9e123b92bb4
Author:jiangxin<jiangxin@f79726c4-f016-41bd-acd5-6c9acb7664b2>
Date:Mon Nov 1 05:47:03 2010+0000
initialized.
git-svn-id:file:///path/to/svn/repos/demo/trunk@1
f79726c4-f016-41bd-acd5-6c9acb7664b2
看到Subversion版本库的分支和里程碑都被克隆出来,并保存在refs/remotes下的引用中。在git log的输出中,可以看到Subversion的提交的确被转换为Git的提交。
下面就可以在Git库中进行修改,并在本地提交(用git commit命令)。
$cat README
hello
$echo "I am fne.">>README
$git add-u
$git commit-m "my hack 1."
[master 55e5fd7]my hack 1.
1 files changed,1 insertions(+),0 deletions(-)
$echo "Thank you.">>README
$git add-u
$git commit-m "my hack 2."
[master f1e00b5]my hack 2.
1 files changed,1 insertions(+),0 deletions(-)
对工作区中的README文件修改了两次,并进行了本地的提交。查看这时的提交日志,会发现最新的两个提交和之前的历次提交略有不同,最新的两个提交的提交说明中不包含git-svn-id:标记。
$git log
commit f1e00b52209f6522dd8135d27e86370de552a7b6
Author:Jiang Xin<jiangxin@ossxp.com>
Date:Thu Nov 4 15:05:47 2010+0800
my hack 2.
commit 55e5fd794e6208703aa999004ec2e422b3673ade
Author:Jiang Xin<jiangxin@ossxp.com>
Date:Thu Nov 4 15:05:32 2010+0800
my hack 1.
commit 1863f91b45def159a3ed2c4c4c9428c25213f956
Author:jiangxin<jiangxin@f79726c4-f016-41bd-acd5-6c9acb7664b2>
Date:Mon Nov 1 05:49:41 2010+0000
hello
git-svn-id:file:///path/to/svn/repos/demo/trunk@2
f79726c4-f016-41bd-acd5-6c9acb7664b2
commit 2c73d657dfc3a1ceca9d465b0b98f9e123b92bb4
Author:jiangxin<jiangxin@f79726c4-f016-41bd-acd5-6c9acb7664b2>
Date:Mon Nov 1 05:47:03 2010+0000
initialized.
git-svn-id:file:///path/to/svn/repos/demo/trunk@1
f79726c4-f016-41bd-acd5-6c9acb7664b2
现在就可以向Subversion服务器推送改动了。但在真实的环境中,往往在向服务器推送时,已经有其他用户在服务器上进行了提交,而且更糟的是,先于我们的提交会造成我们的提交冲突!现在就人为地制造一个冲突:使用svn命令在Subversion版本库中执行一次提交。
$svn checkout file:///path/to/svn/repos/demo/trunk demo
A demo/README
取出版本4。
$cd demo/
$cat README
hello
$echo "HELLO.">README
$svn commit-m "hello->HELLO."
正在发送README
传输文件数据.
提交后的版本为5。
好的,已经模拟了一个用户先于我们更改了Subversion版本库。现在回到用git-svn克隆的本地版本库,执行git svn dcommit操作,将Git中的提交推送到Subversion版本库中。
$git svn dcommit
Committing to file:///path/to/svn/repos/demo/trunk…
事务过时:文件"/trunk/README"已经过时at/usr/lib/git-core/git-svn line 572
显然,由于Subversion版本库中包含了新的提交,导致执行git svn dcommit出错。这时须执行git svn fetch命令,以从Subversion版本库获取更新。
$git svn fetch
M README
r5=fae6dab863ed2152f71bcb2348d476d47194fdd4(refs/remotes/trunk)
$git status
On branch master
nothing to commit(working directory clean)
当获取了新的Subversion提交之后,需要执行git svn rebase将Git中未推送到Subversion的提交通过变基操作转化为继Subversion最新提交的线性提交。这是因为Subversion的提交都是线性的。
$git svnrebase
First,rewinding head to replay your work on top of it…
Applying:my hack 1.
Using index info to reconstruct a base tree…
Falling back to patching base and 3-way merge…
Auto-merging README
CONFLICT(content):Merge conflict in README
Failed to merge in the changes.
Patch failed at 0001 my hack 1.
When you have resolved this problem run "git rebase—continue".
If you would prefer to skip this patch,instead run "git rebase—skip".
To restore the original branch and stop rebasing run "git rebase—abort".
rebase refs/remotes/trunk:command returned error:1
果不其然,变基时发生了冲突,这是因为Subversion中他人的修改和我们在Git库中的修改都改动了同一个文件,并且改动了相近的行。下面按照git rebase冲突解决的标准步骤进行,直到成功完成变基操作,具体操作步骤如下。
(1)先编辑README文件以解决冲突。
$git status
Not currently on any branch.
Unmerged paths:
(use "git reset HEAD<file>…" to unstage)
(use "git add/rm<file>…" as appropriate to mark resolution)
#
both modified:README
#
no changes added to commit(use "git add" and/or "git commit-a")
$vi README
(2)处于冲突状态的REAEME文件的内容。
<<<<<<<HEAD
HELLO.
=======
hello
I am fine.
>>>>>>>my hack 1.
(3)下面是修改后的内容,保存退出。
HELLO.
I am fine.
(4)执行git add命令解决冲突。
$git add README
(5)调用git rebase—continue完成变基操作。
$git rebase—continue
Applying:my hack 1.
Applying:my hack 2.
Using index info to reconstruct a base tree…
Falling back to patching base and 3-way merge…
Auto-merging README
(6)看看变基之后的Git版本库日志:
$git log
commit e382f2e99eca07bc3a92ece89f80a7a5457acfd8
Author:Jiang Xin<jiangxin@ossxp.com>
Date:Thu Nov 4 15:05:47 2010+0800
my hack 2.
commit 6e7e0c7dccf5a072404a28f06ce0c83d77988b0b
Author:Jiang Xin<jiangxin@ossxp.com>
Date:Thu Nov 4 15:05:32 2010+0800
my hack 1.
commit fae6dab863ed2152f71bcb2348d476d47194fdd4
Author:jiangxin<jiangxin@f79726c4-f016-41bd-acd5-6c9acb7664b2>
Date:Thu Nov 4 07:15:58 2010+0000
hello->HELLO.
git-svn-id:file:///path/to/svn/repos/demo/trunk@5
f79726c4-f016-41bd-acd5-6c9acb7664b2
commit 1863f91b45def159a3ed2c4c4c9428c25213f956
Author:jiangxin<jiangxin@f79726c4-f016-41bd-acd5-6c9acb7664b2>
Date:Mon Nov 1 05:49:41 2010+0000
hello
git-svn-id:file:///path/to/svn/repos/demo/trunk@2
f79726c4-f016-41bd-acd5-6c9acb7664b2
commit 2c73d657dfc3a1ceca9d465b0b98f9e123b92bb4
Author:jiangxin<jiangxin@f79726c4-f016-41bd-acd5-6c9acb7664b2>
Date:Mon Nov 1 05:47:03 2010+0000
initialized.
git-svn-id:file:///path/to/svn/repos/demo/trunk@1
f79726c4-f016-41bd-acd5-6c9acb7664b2
(7)当变基操作成功完成后,再执行git svn dcommit向Subversion推送Git库中的两个新提交。
$git svndcommit
Committing to file:///path/to/svn/repos/demo/trunk…
M README
Committed r6
M README
r6=d0eb86bdfad4720e0a24edc49ec2b52e50473e83(refs/remotes/trunk)
No changes between current HEAD and refs/remotes/trunk
Resetting to the latest refs/remotes/trunk
Unstaged changes after reset:
M README
M README
Committed r7
M README
r7=69f4aa56eb96230aedd7c643f65d03b618ccc9e5(refs/remotes/trunk)
No changes between current HEAD and refs/remotes/trunk
Resetting to the latest refs/remotes/trunk
(8)推送之后本地Git库中最新的两个提交的提交说明中也嵌入了git-svn-id:标签。这个标签的作用非常重要,在下一节会予以介绍。
$git log-2
commit 69f4aa56eb96230aedd7c643f65d03b618ccc9e5
Author:jiangxin<jiangxin@f79726c4-f016-41bd-acd5-6c9acb7664b2>
Date:Thu Nov 4 07:56:38 2010+0000
my hack 2.
git-svn-id:file:///path/to/svn/repos/demo/trunk@7
f79726c4-f016-41bd-acd5-6c9acb7664b2
commit d0eb86bdfad4720e0a24edc49ec2b52e50473e83
Author:jiang xin<jiang xin@f79726c4-f016-41bd-acd5-6c9acb7664b2>
Date:Thu Nov 4 07:56:37 2010+0000
my hack 1.
git-svn-id:file:///path/to/svn/repos/demo/trunk@6
f79726c4-f016-41bd-acd5-6c9acb7664b2