git迁移到svn时原有历史提交中出现过冲突导致git svn dcommit出错的解决办法
本文原文地址:http://www.cnblogs.com/lzhskywalker/archive/2012/06/11/2544791.html
故事起因
公司要在产品上支持mstp,于是从外面找来一个叫lisa的开源项目,找到之后发现官方站点的仓库已经没人维护也无法检出了,后来公司里的系统工程师不知道从哪找来一份代码,解压缩之后发现居然还保留了一部分历史提交记录,里面有个.git文件夹!
虽然历史提交对我现在已经没啥意义,不过也省了我重新创建仓库了,于是就在原有的git数据库里继续提交我的新代码,因为只有我一个人开发,所以每次只有一个提交,一个分支,完全没冲突。
最近因为要离职了,按照要求,生产出货的软件都要提交的公司svn仓库统一维护,于是我开始用git svn进行代码迁移。另外几个完全我自己写的小项目都很顺利,可是到mstp这里的时候问题出现了。
先大致说明一下迁移的过程,步骤如下:
1. 用git svn克隆svn仓库到一个新的目录
git svn clone SVNRepoURL migration
2. 进入新的目录,添加原有git仓库为远程分支。此时当前工作目录是个git仓库,不过和svn仓库有个关联,利用了git的远程仓库管理功能。我原有的git仓库是存放在自己假设的gitolite服务器上。
cd migration
git remote add gitrepo git@gitserver:mstp
3. 将git服务器上的代码检出。因为我只有一个分支mast,是基于lisa以前的历史继续演进的,所以我只要把服务器的master分支checkout到本地即可。
git fetch gitrepo master:prepformerge
git checkout prepformerge
4. 因为先前这个仓库数据库的建立,使用的是克隆svn仓库生成的,所以当前的本地master分支其实就是svn仓库的一个映射,所以下面要做的就是对上一步拿出来的分支代码进行rebase操作,将提交基线重新调整为以svn仓库的头一次提交为起始点,后续历史依次从该起始店演进。正常情况下操作如下
git rebase master
5. 上一步如果不出现什么错误,那么此时本地分支prepformerge已经是一个以svn仓库第一次提交为基线的历史提交线了,可以直接提交了。
git svn dcommit
至此全部迁移完成,svn仓库已经具有了git服务器上这个分支的全部提交历史。
但是,事实总不尽如人意……
问题:第4步命令执行失败,出现冲突提示如下:
Applying: pass testcase 2,3,4 update Using index info to reconstruct a base tree... Falling back to patching base and 3-way merge... CONFLICT (modify/delete): test/example.txt deleted in pass testcase 2,3,4 update and modified in HEAD. Version HEAD of test/example.txt left in tree. Failed to merge in the changes. Patch failed at 0024 pass testcase 2,3,4 update When you have resolved this problem run "git rebase --continue". If you would prefer to skip this patch, instead run "git rebase --skip". To check out the original branch and stop rebasing run "git rebase --abort".
这个问题出现过好几次,大意就是重新apply某一此提交的时候和当前最新提交出现冲突。
当时我比较好奇,重新定义基线只不过是调整起点,为啥会冲突呢。仔细看了一下错误提示,最后提示可以用skip参数跳过冲突,尝试了几次依然存在不同冲突,再进一步查看log之后,我恍然大悟,原来rebase操作没那么简单,它是在确定了新的起始点之后,将后续所有提交历史重新提交到新起点上,进行一次重新演进。
当执行rebase操作时,git会切换到一个新的无名分支上(出错之后git branch发现的),然后以rebase的目标起点为起点,将执行rebase命令时所在的分支上的所有历史提交一次一次提交到新的无名分支上。一切顺利的话,git会用无名分支覆盖原有的期望被rebase的分支。
问题的根源找到了,由于我是基于lisa开源项目的git仓库历史继续提交的,我完全不知道该项目历史提交中曾经出现过多次冲突和merge,后来在log中才发现这一点。这就导致了我此次rebase操作时,由于是重现历史的操作,以前lisa的开发人员搞出来的冲突必然被重现了出来,于是我就看到了类似上面代码框里的冲突提示。最开始我不明白为什么会冲突,而且我以为冲突可以忽略或解决。后来我尝试过每次冲突都手动解决,选择最新提交;或者手动解决选择保持上一次提交;或者选择每次都跳过冲突。最后发现无论如何解决,最终rebase能成功,但是在最后一步提交svn时始终会有下面的错误提示:
Committing to http://10.0.1.16/svn/software/oceanway/mstp/trunk/source ... A Makefile A attic/cli2dump.c A attic/command.c ......此处省略N行 A mstp 5bb84509ee2780bcb9ace241907cd630c9960ed8 doesn't exist in the repository at /usr/libexec/git-core/git-svn line 5217 Failed to read object 5bb84509ee2780bcb9ace241907cd630c9960ed8 at /usr/libexec/git-core/git-svn line 846
找了一下无论是git log还是git show都确实无法找到错误提示里出现的那个hash,grep git的数据仓库文件也找不到。看来是因为前面merge冲突的时候引发的,google的结果全部都是说因为使用了submodule导致,可是我的仓库很简单,只有一个分支,不过是曾经有过冲突而已,根本没有使用submodule。想来想去不知道如何解决,也不知道是不是前面的操作起始应该可以解决这个错误但是我操作步骤不对。所以最后想到一个办法,把lisa的历史提交全部抛弃,从我自己的第一次提交开始作为新基线重新rebase。
在同事的帮助下,经过一番实验,得到一个方法:
1. 首先将git仓库重新克隆至某个地方
git clone git@gitserver:mstp
2. 进入目录,创建新的分支,用作后面的重新定义基线
cd mstp
git checkout -b forrebase
3. 退到上一层目录,拷贝仓库目录为新的仓库,将历史reset到我自己的第一次提交,并删除.git目录后重新创建git仓库。即创建一个和原有git仓库完全无关的新仓库,并且创建一个起始节点内容和原有仓库中我自己的第一次提交内容完全一致的节点。
cd .. cp -r mstp tmp cd tmp git reset --hard myfirstcommit rm .git -rf git init git add . git ci -a -m "[INIT]"
4. 进入原有仓库,将上一步创建的新仓库作为远程分支添加,并检出其主分支
cd ../mstp git remote add tmp ../tmp git fetch tmp master:newbaseline
5. 切回要重新定义基线的分支,rebase
git checkout forrebase
git rebase newbaseline
这一步同样还可能会出现一些冲突,因为毕竟这个新选择的起点完全不在以前的历史线上。这时候根据提示用git rebase --skip跳过这些冲突即可。最终演进到我自己的提交时能够全部顺利通过,然后再将这个forrebase分支推送到git服务器。
回到最初迁移svn的步骤,重新做一遍前面的操作,rebase到映射svn的master分支时选用新的forrebase分支,这时候dcommit提交svn就没有问题了。重复操作如下:
git svn clone SVNRepoURL migration
cd migration
git remote add gitrepo git@gitserver:mstp
git fetch gitrepo forrebase:prepformerge
git checkout prepformerge
git rebase master
git svn dcommit