在捣鼓我的 Gregarius 时,发现无法读取 HTTPS 的 RSS , 追查发现是他所使用的 HTTP 客户端类 Snoopy 的原因。 想升级新版 Snoopy 却发现原作者已经几年都不更新了, Github 上倒是有人弄了几个镜像, 其中 hurrycaner 的这个 还对 README 进行了一些改进。 但所有镜像都没有 SourceForge 上的修改历史。
所以,我想作的是,基于 hurrycaner 的镜像进行 Fork, 但是要把 SourceForge 上的修改历史也弄进来。
CVS –> Git
现在应该没有人用 CVS 了把,SourceForge 也支持 Git 了, 但上面有些古老项目依然只有 CVS 。
把 CVS 转换成 Git 的工具还是有一些的,但从 一些讨论看来 似乎都做不到完美。 也难怪,CVS 的存储格式实在是有些奇怪, 代码、修改记录、修改注释都堆在一个文件中,解析起来肯定头疼。
由于害怕 cvs2git 会像 svn2git 那样转换时把作者缀上 UUID, 我先试了试 parsecvs , 但这货连使用说明都没有,放弃了。 然后用的是 StackOverflow 上最后一个人推荐的 crap 。 和上面的一样,都是简单 make 一下就有可执行文件用, 但比上面的帮助全,还有一个非常简单的例子。
这就可以开始了,先把 SourceForge 上的仓库下载下来:
$ mkdir Snoopy.cvs $ rsync -av rsync://snoopy.cvs.sourceforge.net/cvsroot/snoopy/ Snoopy.cvs receiving incremental file list ./ CVSROOT/ CVSROOT/.#checkoutlist CVSROOT/.#commitinfo CVSROOT/.#config CVSROOT/.#cvswrappers CVSROOT/.#editinfo CVSROOT/.#loginfo CVSROOT/.#modules CVSROOT/.#notify CVSROOT/.#rcsinfo CVSROOT/.#taginfo CVSROOT/.#verifymsg CVSROOT/checkoutlist CVSROOT/checkoutlist,v CVSROOT/commitinfo CVSROOT/commitinfo,v CVSROOT/config CVSROOT/config,v CVSROOT/cvswrappers CVSROOT/cvswrappers,v CVSROOT/editinfo CVSROOT/editinfo,v CVSROOT/history CVSROOT/loginfo CVSROOT/loginfo,v CVSROOT/modules CVSROOT/modules,v CVSROOT/notify CVSROOT/notify,v CVSROOT/passwd CVSROOT/rcsinfo CVSROOT/rcsinfo,v CVSROOT/readers CVSROOT/taginfo CVSROOT/taginfo,v CVSROOT/val-tags CVSROOT/verifymsg CVSROOT/verifymsg,v CVSROOT/writers CVSROOT/Emptydir/ Snoopy/ Snoopy/AUTHORS,v Snoopy/COPYING.lib,v Snoopy/ChangeLog,v Snoopy/FAQ,v Snoopy/INSTALL,v Snoopy/Makefile.am,v Snoopy/NEWS,v Snoopy/README,v Snoopy/Snoopy.class.php,v Snoopy/TODO,v Snoopy/autogen.sh,v Snoopy/configure.in,v Snoopy/Attic/ Snoopy/Attic/.cvsignore,v Snoopy/Attic/COPYING,v Snoopy/Attic/Snoopy.class.inc,v sent 1,066 bytes received 229,013 bytes 17,042.89 bytes/sec total size is 225,573 speedup is 0.98 |
注意这和下载 CVS 代码是不一样的,这里下载的是 CVSROOT,仓库的原始码。
然后初始化一个 Git 仓库目录,用 crap 开始转换:
$ mkdir Snoopy.git $ cd Snoopy.git $ git init $ ../crap/crap-clone /home/fwolf/dev/Snoopy.cvs Snoopy Valid-requests Root Valid-responses valid-requests Repository Directory Max-dotdot Static-directory Sticky Entry Kopt Checkin-time Modified Is-modified Empty-conflicts UseUnchanged Unchanged Notify Questionable Argument Argumentx Global_option Gzip-stream wrapper-sendme-rcsOptions Set Gssapi-authenticate expand-modules ci co update diff log rlog add remove update-patches gzip-file-contents status rdiff tag rtag import admin export history release watch-on watch-off watch-add watch-remove watchers editors init annotate rannotate noop version *********** CYCLE ********** Changeset andrei *** empty log message *** INSTALL:1.1 Makefile.am:1.1 NEWS:1.1 autogen.sh:1.1 configure.in:1.1 .cvsignore:1.1 Deferring: autogen.sh:1.2 Tag 'Snoopy' placing on branch '' Tag 'start' placing on branch 'Snoopy' opening version cache failed: No such file or directory 1970-01-01 08:00:00 CST BRANCH 2000-02-03 23:40:59 CST COMMIT 2000-02-03 23:40:59 CST BRANCH Snoopy 2000-02-03 23:40:59 CST COMMIT 2000-02-03 23:40:59 CST COMMIT 2000-02-03 23:40:59 CST TAG start 2000-02-04 00:10:54 CST COMMIT 2000-02-04 00:10:54 CST COMMIT 2000-02-04 00:28:59 CST COMMIT 2000-02-22 23:44:57 CST COMMIT 2000-03-10 04:52:59 CST COMMIT 2000-03-10 04:54:47 CST COMMIT 2000-05-18 22:50:14 CST COMMIT 2000-05-18 23:36:34 CST COMMIT 2000-05-18 23:44:00 CST COMMIT 2000-06-30 02:37:25 CST COMMIT 2000-08-23 04:36:52 CST COMMIT 2000-09-14 04:52:04 CST COMMIT 2000-09-14 22:09:58 CST COMMIT 2000-09-15 21:11:11 CST COMMIT 2000-09-16 05:57:37 CST COMMIT 2000-09-27 03:34:38 CST COMMIT 2000-09-27 04:28:45 CST COMMIT 2000-10-09 21:13:52 CST COMMIT 2001-03-25 04:15:18 CST COMMIT 2001-07-07 05:24:11 CST COMMIT 2001-08-22 23:43:24 CST COMMIT 2001-11-21 04:23:02 CST COMMIT 2002-10-03 22:38:49 CST COMMIT 2002-10-03 22:55:06 CST COMMIT 2002-10-03 22:57:39 CST COMMIT 2002-10-10 04:25:50 CST COMMITMissed first time round: ChangeLog 1.11 Missed first time round: Snoopy.class.inc 1.21 2002-10-10 04:41:24 CST COMMITcvs checkout ChangeLog 1.14 - version is duplicate cvs checkout Snoopy.class.inc 1.24 - version is duplicate Missed first time round: ChangeLog 1.12 Missed first time round: Snoopy.class.inc 1.22 2002-10-10 04:51:57 CST COMMITcvs checkout ChangeLog 1.14 - version is duplicate cvs checkout Snoopy.class.inc 1.24 - version is duplicate Missed first time round: ChangeLog 1.13 Missed first time round: Snoopy.class.inc 1.23 2002-10-10 04:56:14 CST COMMIT 2003-03-12 22:40:55 CST COMMIT 2003-09-15 21:58:28 CST COMMIT 2003-10-22 03:18:39 CST COMMIT 2003-11-08 03:52:58 CST COMMIT 2003-12-24 03:34:35 CST COMMIT 2004-01-08 03:16:10 CST COMMIT 2004-07-25 02:23:27 CST COMMITMissed first time round: ChangeLog 1.19 Missed first time round: Snoopy.class.php 1.5 2004-07-25 02:34:28 CST COMMITcvs checkout ChangeLog 1.22 - version is duplicate cvs checkout Snoopy.class.php 1.8 - version is duplicate Missed first time round: ChangeLog 1.20 Missed first time round: Snoopy.class.php 1.6 2004-07-25 08:49:02 CST COMMIT 2004-07-25 10:42:48 CST COMMIT 2004-07-25 10:46:34 CST COMMIT 2004-07-25 10:46:59 CST COMMIT 2004-07-25 11:18:32 CST COMMIT 2004-10-16 13:14:11 CST COMMIT 2004-10-16 13:17:41 CST COMMIT 2004-10-16 13:44:51 CST COMMIT 2004-10-16 14:27:09 CST COMMIT 2004-10-16 14:28:30 CST COMMIT 2004-10-16 14:40:42 CST COMMIT 2004-10-17 00:33:58 CST COMMIT 2004-10-17 00:36:18 CST COMMIT 2004-10-18 13:12:55 CST COMMIT 2004-10-18 13:18:27 CST COMMIT 2004-10-18 13:19:04 CST COMMIT 2004-10-18 13:19:28 CST COMMIT 2004-10-18 13:19:51 CST COMMIT 2004-11-18 13:51:32 CST COMMIT 2004-11-18 13:52:28 CST COMMIT 2004-11-18 14:37:05 CST COMMIT 2005-02-03 12:43:26 CST COMMIT 2005-02-03 12:57:05 CST COMMIT 2005-10-23 10:08:40 CST COMMIT 2005-10-23 10:16:26 CST COMMIT 2005-10-24 00:30:34 CST COMMIT 2005-10-24 23:34:50 CST COMMIT 2005-10-24 23:44:12 CST COMMIT 2005-10-24 23:44:59 CST COMMIT 2005-10-24 23:46:10 CST COMMIT 2005-10-30 13:33:15 CST COMMIT 2005-10-30 13:45:09 CST COMMIT 2005-10-31 02:32:42 CST COMMIT 2005-10-31 02:51:35 CST COMMIT 2005-11-08 14:53:56 CST COMMIT 2005-11-08 15:01:47 CST COMMIT 2008-10-22 23:30:41 CST COMMIT 2008-10-22 23:53:14 CST COMMIT 2008-11-09 05:09:09 CST COMMIT Emitted 79 commits (= total 79). Exact 2 + 1 = 3 branches + tags. Fixup 0 + 0 = 0 branches + tags. Download 147 cvs versions in 84 transactions. String cache: 141 items, 132/1024 buckets used, mean search 1.06383 git-fast-import statistics: --------------------------------------------------------------------- Alloc'd objects: 5000 Total objects: 289 ( 8 duplicates ) blobs : 134 ( 7 duplicates 46 deltas of 133 attempts) trees : 77 ( 0 duplicates 70 deltas of 71 attempts) commits: 78 ( 1 duplicates 0 deltas of 0 attempts) tags : 0 ( 0 duplicates 0 deltas of 0 attempts) Total branches: 3 ( 2 loads ) marks: 1024 ( 220 unique ) atoms: 15 Memory total: 2294 KiB pools: 2098 KiB objects: 195 KiB --------------------------------------------------------------------- pack_report: getpagesize() = 4096 pack_report: core.packedGitWindowSize = 33554432 pack_report: core.packedGitLimit = 268435456 pack_report: pack_used_ctr = 7 pack_report: pack_mmap_calls = 3 pack_report: pack_open_windows = 1 / 1 pack_report: pack_mapped = 350104 / 350104 --------------------------------------------------------------------- |
这样这个 Git 仓库就包含了已经转换过了的 CVS 历史记录, 如果看不到文件可以 reset 一下。
按说后续的操作理论上可以在这个仓库目录中操作,但为了更好的和 Fork 的项目合并, 我使用导出 Patch 的方法,后面再 am:
$ git log --pretty=oneline |wc -l 78 $ git format-patch -78 |
其实在这里,也可以在目标 repo 里面,通过添加 Snoopy.git 为 Git remote, 然后 merge remote 的方式进行,效果更好,还不用修改提交时间。
Fork 项目,移花接木
在 Github 上 Fork https://github.com/hurrycaner/snoopy , 得到 https://github.com/fwolf/snoopy , 但先不下载到本地,后面的操作方法和正常 Fork 项目是 不一样 的。
在本地再新建一个 Git 仓库,这个仓库是我们今后维护 Snoopy 的主仓库:
$ mkdir Snoopy $ cd Snoopy $ git init $ git remote add origin git@github.com:fwolf/snoopy.git $ touch .gitignore $ git add .gitignore $ git commit -a -m "Initial commit" $ git push -f origin master |
和新建项目的方法基本一样,不同点是我们的 origin 是 Fork 后的项目, 并且进行了 push -f 操作,覆盖掉了 hurrycaner 的所有提交。
接下来新建一个 sourceforge 分支,保留 SourceForge 上 CVS 代码的最终状态, 提交是通过 am 导入的, --committer-date-is-author-date 参数是将作者的时间作为提交时间, 也可以不要。Patch 0002 是空的,会导致 am 失败,所以删除掉:
$ git branch sourceforge $ git checkout sourceforge $ rm ../Snoopy.git/0002-Initial-check-in.patch $ git am ../Snoopy.git/00* --committer-date-is-author-date $ git checkout master $ git merge sourceforge $ git push |
现在,master 分支上是我作的一个初始提交,加上 CVS 上导过来的提交内容, 相当于是 CVS 被完整的导入了 Git。
添加只有一个空 .gitignore 文件的初始提交是 Git 的一个习惯, 因为 Git 的初始提交可以视为是“不可以操作”的, 所以最好是空或者只包含最少内容。
接下来,我们要将 hurrycaner 所作的修改合并进来。 由于他是基于 Snoopy 1.2.4 代码修改的, 和我导入的最终代码差距不大,所以合并还比较顺利,只有几处冲突而已:
$ git branch hurrycaner $ git checkout hurrycaner $ git remote add upstream git@github.com:hurrycaner/snoopy.git $ git fetch upstream $ git merge upstream/master # 手工解决冲突 $ git checkout master $ git merge hurrycaner $ git push |
这就基本上完成了,保留了从 CVS 到 hurrycaner 的完整修改记录, 并且还能像正常 Fork 的项目那样继续工作。
修改记录看起来是这个样子的:
我已经向原项目作者推送 Pull Request 了。 hurrycaner 在 Github 上并不活跃,不知道能不能看到、会不会收啊。
尾声
Git 的使用是比较灵活的,我相信其他分布式 SCM 也能做到,没研究过,不对比。 话说回来,本文中的做法,是不是有点鸠占鹊巢的感觉?