9.2. Git 与 CVS 面对面
9.2.1. 面对面访谈录
Git:我的提交是原子提交。每次提交都对应于一个目录树(树对象)。因为我的提交ID是对目录树及相关的提交信息建立的一个SHA1哈希值,所以可以保证数据的完整性。
CVS:我承认这是我的软肋,一次错误或冲突的提交会导致部分数据被提交,而部分数据没有提交,版本库完整性被破坏,所以人们才设计出来Subversion(SVN)来取代我。
Git:我的分支和里程碑管理非常快捷。因为我的分支和里程碑就是一个记录提交ID的引用,你的呢?
CVS:你怎么又提到别人的痛处了!我的分支和里程碑创建速度还是很快的,...嗯...,如果在版本库中只有几个文件的话。当然如果版本库的文件的很多,创建分支、里程碑创建就需要花费更多的时间。有些人对此忍无可忍,于是设计出SVN来取代我。
Git:其实我不用里程碑都没有关系,因为每一个提交ID就对应于唯一的一个提交状态。
CVS:这也是我做不到的。我没有全局版本号的概念,每一个文件都通过单独的版本号记录其变更历史,所以人们在使用我的时候必须经常地用里程碑(tag)对我的状态进行标识。还需要提醒一句,如果版本库中文件太多,创建里程碑是很耗时的,因为要一一打开每一个版本库中的文件,在其中记录里程碑和文件版本的关系。
Git:我的工作区很干净。只在工作区的根目录下有一个:file:`.git`目录,此外再无其他辅助目录或文件。
CVS:我要在工作区的每一个目录下都放置一个CVS目录,这个目录下有个Entries
文件很重要,记录了对应工作区文件的检出版本以及时间戳等信息。这样做的好处是可以将工作区移动到任何其他磁盘和目录,依然可以使用,甚至我可以将工作区的一个子目录拿出来,作为独立的工作区。
Git:我也可以将工作区移动到其他磁盘,但是要保证工作区下的:file:`.git`目录和工作区一同移动。也不可以只移动工作区下的一个目录到其他磁盘或目录,那样的话移出的目录就不能工作了。
Git:我的网络传输效率很高。在和其他版本库交互时,对方会告诉我他有什么,我也知道我有什么,因为只对缺失对象的打包传输,所以效率很高而且能够显示传输进度。
CVS:这一点我不行。因为我本地没有文件做对照,所以我在传输的时候不可能做到增量传输。
Git:我甚至可以不需要网络,因为我在本地拥有完整的版本库,几乎所有操作都是在本地完成。
CVS:我的操作处处需要网络,如果版本库是在网络中其他服务器上的话。如果网速比较慢,查看日志、查看历史版本都需要花费很长时间等待。
CVS:你怎么没有更新(update)命令?还有你为什么老是要执行检出命令(checkout)?对我而言,检出命令只在工作区创建时一次完成的。
Git:你的检出命令(checkout)是从远程版本库服务器获取数据完成本地工作区的创建,版本库仍然位于远程的服务器上。你的更新(update)命令执行的很慢对么?之所以你需要执行更新命令是因为你的版本库在远程啊。别忘了我的版本库是在本地,我的每一步操作工作区和版本库都是同步的,所以更新操作就没有存在的必要了。而我的检出(checkout)操作是将本地版本库的数据检出到本地工作区,用于恢复本地丢失的文件或错误改动的文件,也用于切换不同的分支。我也有一个和你的更新(update)操作类似的比较耗时的网络操作命令叫做:command:`git fetch`或:command:`git pull`,这两个操作是从别人的版本库获取他人改动。一般使用我(Git)做团队协作的时候,会部署一个集中共享的版本库,我就用这两个命令(:command:`git fetch`或:command:`git pull`)从共享的版本库执行拉回操作。也也许你(CVS)会觉得:command:`git fetch`或者:command:`git pull`和你的:command:`cvs update`命令更像吧。至于你的检出命令(:command:`cvs checkout`),实际上和我克隆命令(:command:`git clone`)很相似,只不过我的克隆命令不但创建了本地工作区,而且在本地还复制了和远程版本库一样的本地版本库。
CVS:为什么你的检入命令(commit)命令执行的那么快?
Git:是的,我的检入命令飞一般就执行完了,也是因为版本库就在本地。也许你(CVS)会觉得我的推送命令(:command:`git push`)和你的检入命令(:command:`cvs commit`)更相像,其实这是一个误会。如果我不做本地提交,是不能通过推送命令(:command:`git push`)将我的本地的提交共享给(推送给)其他版本库的。你(CVS)每一次提交都要和版本库进行网络通讯,而我可以在本地版本库进行多次提交,直到我的主人想喝咖啡了才执行一次:command:`git push`,将我本地版本库中新的提交推送给远程版本库。
CVS:我每一个文件都一个独立的版本号,你有么?
Git:每一个文件一个版本号?这有什么值得夸耀的?我听说你最早是用脚本对RCS系统进行封装实现的,所以你每个文件都有一个独立的版本控制,这让你变得很零碎。我听说某些商业版本控制系统也是这样,真糟糕。我的每一次提交都有一个全球唯一的版本号,这样不但是在本地版本库中是唯一的,和其他人的版本库也不会有冲突。
CVS:我能一次检出一个目录,你好像不能吧?
Git:所以我有子模组,以及repo等第三方工具,可以帮助我把一个大的版本库拆开多个版本库组合来使用啊。
CVS:我能添加空目录,你好像不能吧!
Git:是的,我现在还不能记录空目录。但是用户可以在空目录下创建一个隐含文件,并将该隐含文件添加到版本库中,也就实现了空目录添加的功能。你,CVS,目录管理是你的软肋,你很难实现目录的重命名,而目录重命名对我来说是小菜一碟。
9.2.2. CVS和Git命令对照
比较项目 | CVS命令 | Git命令 |
---|---|---|
URL | :pserver:user@host:/path/to/cvsroot | git://host/path/to/repos.git |
/path/to/cvsroot | ssh://user@host/path/to/repos.git | |
user@host:path/to/repos.git | ||
file:///path/to/repos.git | ||
/path/to/repos.git | ||
版本库初始化 | cvs -d <path> init | git init [–bare] <path> |
导入数据 | cvs -d <url> import -m ... | git clone; git add .; git commit |
版本库检出 | cvs -d <url> checkout [-d <path>] <module> | git clone <url> <path> |
版本库分支检出 | cvs -d <url> checkout -r <branch> <module> | git clone -b <branch> <url> |
工作区更新 | cvs update | git pull |
更新至历史版本 | cvs update -r <rev> | git checkout <commit> |
更新到指定日期 | cvs update -D <date> | git checkout HEAD@’{<date>}’ |
更新至最新提交 | cvs update -A | git checkout master |
切换至里程碑 | cvs update -r <tag> | git checkout <tag> |
切换至分支 | cvs update -r <branch> | git checkout <branch> |
还原文件/强制覆盖 | cvs up -C <path> | git checkout – <path> |
添加文件 | cvs add <TextFile> | git add <TextFile> |
添加文件(二进制) | cvs add -kb <BinaryFile> | git add <BinaryFile> |
删除文件 | cvs remove -f <path> | git rm <path> |
移动文件 | mv <old> <new>; cvs rm <old>; cvs add <new> | git mv <old> <new> |
反删除文件 | cvs add <path> | git add <path> |
工作区差异比较 | cvs diff -u | git diff |
git diff –cached | ||
git diff HEAD | ||
版本间差异比较 | cvs diff -u -r <rev1> -r <rev2> <path> | git diff <commit1> <commit2> – <path> |
查看工作区状态 | cvs -n up | git status |
提交 | cvs commit -m “<msg>” | git commit -a -m “<msg>” ; git push |
显示提交日志 | cvs log <path> | less | git log |
逐行追溯 | cvs annotate | git blame |
显示里程碑/分支 | cvs status -v | git tag |
git branch | ||
git show-ref | ||
创建里程碑 | cvs tag [-r <rev>] <tagname> . | git tag [-m “<msg>”] <tagname> [<commit>] |
删除里程碑 | cvs rtag -d <tagname> | git tag -d <tagname> |
创建分支 | cvs rtag -b -r <rev> -b <branch> <module> | git branch <branch> <commit> |
git checkout -b <branch> <commit> | ||
删除分支 | cvs rtag -d <branch> | git branch -d <branch> |
导出项目文件 | cvs -d <url> export -r <tag> <module> | git archive -o <output.tar> <tag> <path> |
git archive -o <output.tar> –remote=<url> <tag> <path> | ||
分支合并 | cvs update [-j <start>] -j <end>; cvs commit | git merge <branch> |
显示文件列表 | cvs ls | git ls-files |
cvs -d <url> rls -r <rev> | git ls-tree <commit> | |
更改提交说明 | cvs admin -m <rev>:<msg> <path> | git commit –amend |
撤消提交 | cvs admin -o <range> <path> | git reset [ –soft | –hard ] HEAD^ |
杂项 | .cvsignore 文件 | .gitignore 文件 |
参数 -kb 设置二进制模式 | -text 属性 | |
参数 -kv 开启关键字扩展 | export-subst 属性 |