目录:
Subversion 用户眼中的 Git (1):
集中式 vs 分布式
Subversion 用户眼中的 Git (2):
版本库, 工作区如影随形
Subversion 用户眼中的 Git (3):
命令集不兼容
Subversion 用户眼中的 Git (4):
全局版本号和全球版本号
Subversion 用户眼中的 Git (5):
没有部分检出
Subversion 用户眼中的 Git (6):
stage
Subversion 用户眼中的 Git (7):
完全不同的分支和里程碑的实现
Subversion 用户眼中的 Git (8):
SVN没有后悔药,git有好多
Subversion 用户眼中的 Git (9):
单亲 VS 多亲
Subversion 用户眼中的 Git (10):
Git 命令行的人性化设计
Subversion用户眼中的 Git (1): 集中式 vs 分布式
“Git 很古怪” —— 使用 Subversion 的用户说道。 那么从
Subversion 用户的角度来看,Git有哪些古怪之处,或者说特别之处呢? 我们将会以连载的方式一一道来。
如果您有什么建议和补充,或者想知道 Subversion 中的某个 git 对应物,可以在博客后留言 …oO
Subversion 属于集中式的版本控制系统: ?每个版本库有唯一一个“官方地址”,每个用户都从这个唯一地址获取代码、数据;
- 获取代码库的更新,也只能连接到这个唯一的代码库,同步以取得最新数据;
- 提交必须有网络连接(非本地版本库);
- 提交需要授权,如果没有授权,提交失败;
- 提交并非每次都能够成功。如果有其他人先于你提交,会提示“改动基于过时的版本,先更新再提交”…
诸如此类
- 冲突解决是一个提交速度的竞赛:手快者,先提交,平安无事;手慢者,后提交,可能遇到麻烦的冲突解决
Git 属于分布式的版本控制系统:
- 众生平等,每个检出(checkout)的版本库,或者更准确的说每个克隆(clone)的版本库都是平等的。
你可以从任何一个版本库的克隆来创建属于你自己的版本库,同时你的版本库也可以作为源提供给他人,只要你愿意。
- 获取版本库的更新,可以来自任何源。 你可以从张三那里获得上游的改动,包括张三自己的提交;你也可以从李四那里获得上游的改动,也可能包括李四的提交。
- 提交完全在本地完成。无须别人给你授权,你的版本库你作主。 当然你在你的版本库中的改动是否别人愿意合并到他们的版本库则是另外的一回事了。
- 提交总是会成功,因为提交是在本地进行的么。 甚至基于旧版本的改动也可以成功提交,提交会基于旧的版本创建一个新的分支
- 冲突解决不再像是SVN一样的提交竞赛,而是在需要的时候才进行合并和冲突解决]
- Subversion的提交竞赛,在多人协作开发时,提交经常被打断。坏的体验 :-(
- Git 的每个用户就好像工作在独立的 Feature Branch (功能分支)中
- Git的提交不会被打断,直到你的工作完全满意了,PUSH给他人或者他人PULL你的版本库
合并会发生在PULL和PUSH过程中,不能自动解决的冲突会提示您手工完成
Git 也可以模拟集中式的工作模式
- Subversion只有一种集中式的工作模式 所有人都和服务器同步,提交直接到服务器上
- Git 也可以模拟集中式的工作模式 ?Git版本库统一放在服务器中
- 可以为 Git 版本库进行授权:谁能创建版本库,谁能向版本库PUSH,谁能够读取(克隆)版本库
- 团队的成员先将服务器的版本库克隆到本地;并经常的从服务器的版本库拉(PULL)最新的更新;
- 团队的成员将自己的改动推(PUSH)到服务器的版本库中,当其他人和版本库同步(PULL)时,会自动获取改变
- Git 的集中式工作模式非常灵活
- 你完全可以在脱离Git服务器所在网络的情况下,如移动办公/出差时,照常使用代码库
- 你只需要在能够接入Git服务器所在网络时,PULL和PUSH即可完成和服务器同步以及提交
- Git提供 rebase 命令,可以让你的改动看起来是基于最新的代码实现的改动
- Git 有更多的工作模式可以选择,远非 Subversion可比
Subversion 用户眼中的 Git (2): 版本库, 工作区如影随形
Subversion 的工作区和版本库截然分开,工作区中的修改要提交到版本库,可能是本机另外一个目录的版本库,也可能是通过网络连接到服务器上的版本库。
而 Git 的工作区和版本库是如影随形的。没有使用过分布式版本控制系统的 Subversion 用户可能会感到困惑,也可能将如影随形的
.git 目录看作是 Subversion 工作区中的 .svn 目录的等价物,那可就错了…
Git 的版本库和工作区如影随形
Subversion 的工作区和版本库物理上分开:
- Subversion的版本库和工作区是存储在不同路径下,一般是在不同的主机中
- Subversion的企业级部署中,版本库在服务器上,只能通过 https, http, svn
等协议访问,而不能直接被用户接触到
- Subversion的工作区是一份版本库在某个状态下的快照,如:版本库最新的数据检出到工作区
- Subversion的工作区中每一个目录下都包含一个名为 .svn 的控制目录(隐藏的目录),该目录的作用是:
- 标识工作区和版本库的对应关系。参见文件 .svn/entries
- 包含一份该子目录下检出文件的原始拷贝。当文件改动的差异比较或者本地改动的回退时,可以直接参考原始拷贝而无须通过网络访问远程版本库
- Subversion 的 .svn 控制目录,会引入很多麻烦:
- .svn 下的文件原始考本,会导致在目录下按照文件内容搜索时,多出一倍的搜索时间和搜索结果
- .svn 很容易在集成时,引入产品中,尤其是 Web 应用。将 .svn 目录带入Web服务器会导致安全隐患。因为一个不允许目录浏览的Web目录,可以通过
.svn/entries 文件查看到该目录下可能存在的文件,进而 :silly:
Git 的版本库和工作区如影随形,在同一个目录下
- 最常见的模式是工作区和版本库在一起
- 工作区的根目录有一个.git 子目录,这个名为 .git 的目录就是版本库本身,千万不要删除噢
- 工作区中其他文件为工作区文件,可能是从 .git 中检出的,或者要检入的,或者是运行时、临时文件
- 当然版本库可以脱离工作区而存在,成为 bare(赤裸?)版本库。可以用 --bare 参数来创建
- 但是工作区不能脱离版本库而存在,即工作区的根目录下必须有一个名为 .git 的版本库克隆
- Git 的版本库因为就在工作区中,能直接被用户接触到
- 用户可以编辑 .git/config 文件,修改配置,增添新的源
- 用户可以编辑 .git/info/exclude 文件,创建本地忽略…
- Git 的工作区中只在工作区的根目录下有一个 .git 目录,此外再无任何控制目录。
- 像 Subversion的泛滥的 .svn 目录的缺点都不存在
- Git 工作区下唯一的 .git 目录是版本库,并非 .svn 的等价物,如果删除了 .git
目录,而又没有该版本库的其他镜像(克隆)的话,你破坏了整个历史,版本库也永远的失去了。
- Git 在本地的 .git 版本库,提供了完全的改动历史
- 除了和其他人数据交换外,任何版本库相关的操作都在本地完成
- 更多的本地操作,避免了冗长的网络延迟,大大节省了时间。例如:查看 log,切换到任何历史版本等操作都无须任何网络操作。
Git的版本库和工作区混在一起,安全么?
- 本地创建一个Subversion版本库,再在另外的目录检出,心理上感觉很安全。即使工作区删除了,或者工作区所在的分区格式化了,但是版本库仍在啊。
- 本地创建一个Git库,因为工作区和库是在同一个目录中,如果工作区删除了,或者所在的磁盘分区格式化了,数据不是全都没有了么?
其实Git更安全:
- 第一个办法:在一个磁盘分区中创建版本库(最好是用--bare 参数创建),然后在另外的磁盘分区中克隆一个新的作为工作区。在工作区的提交要不时的PUSH到另外分区的版本库,这样就实现了本地的数据镜像。你甚至可以在本地创建更多的版本库镜像,安全性要比Subversion的一个库加上一个工作区安全多了吧。
- 另外的办法:把你的版本库共享给他人,当他人克隆了你的版本库时,你就拥有了一个异地备份。
Subversion 用户眼中的 Git (3): 命令集不兼容
SVN 用户对 Git 的不好的体验,可能大多来自于两者命令集差异很大,不兼容,感觉非常不习惯。
这其中的一部分原因是因为 SVN 和 Git 的原理不同,分属不同阵营——集中式和分布式版本控制;另外一个重要的原因,可能就是
Linus Torvals 痛恨 CVS,而且 Torvals 曾经说过的很有争议话,就是他认为 SVN
也是一个失败。所以,Torvals 设计的 Git 当然要特立独行了。 不过…
由于原理不同,导致 SVN 和 Git 不兼容的命令
svn checkout 和 git clone
这两个命令,都是首次从其他版本库创建本地拷贝时运行的命令,都是只需要执行一次就可以的命令。
- “svn checkout” 就是检出,很形象的比喻,subversion 就是要从服务器的版本库建立本地的工作拷贝(工作区)。检出之后的本地拷贝和版本库有着千丝万缕的联系(每个子目录下的
.svn 目录中的 entries 文件都会标记版本库的地址),本地提交都要上传到服务器版本库中完成。
- “git clone”就是克隆,也是非常形象的比喻。作为分布式版本控制系统,通过克隆创建的本地版本库和远程版本库一模一样,没有谁比谁更好。克隆之后的本地版本库和源版本库有着一丝联系(在
.git/config 中配置 remote版本库的URL),这一丝联系,无非是为了不定期的双方分享改动而已。
svn update 和 git checkout,
git pull
真的很难说和 “svn update” 对应的 git 命令,是 git
checkout? 还是 git pull?
- “svn update” 是从 subversion 版本库更新最新的提交到本地
- “git pull” 是从源版本库拉数据到本地版本库,并和本地对应的分支进行合并。“pull”在这里非常形象,因为这个操作不会将本地版本库的改动传递到源版本库(远程版本库),只会将远程版本库最新的改动
fetch 过来,然后在合并。
- “git checkout” 在某种意义上,也很像。例如像要将本地修改取消或者本地删除的文件还原,在
subversion 中就要用 svn up 命令,而在 git 中,当然用 “git checkout
.” 命令了。
可以通过别名改进兼容性的 Git 命令
一个纯爷们说:
- 这个可以有: git commit, 这个真没有: git ci
- 这个可以有: git checkout, 这个真没有: git co
- 这个可以有: git status, 这个真没有: git st
- 这个可以有: git branch, 这个真没有: git br
如果 Git 真的那么不堪,我也就不再这里写博客,早就洗洗睡了。Git
可以用别名实现
$ git config --global alias.co checkout
$ git config --global alias.br branch $ git config --global
alias.ci commit $ git config --global alias.st status
通过上面的几条命令,在git的全局配置文件中定义了命令别名,这样就可以使用和
CVS/SVN 兼容的短格式命令了。
大同小异,兼容的命令居多
- svn diff 和 git diff git diff 命令自动提供分页显示功能
- svn log 和 git log 如果想要查看提交中的文件变更,svn log -v 可以很好的显示出来,git
log -p 或者git log --stat 显得罗嗦,在这种情况下,可能 git whatchanged
是您需要的
- svn add 和 git add 要注意的是 git add 不但对新文件操作,还可对修改的文件操作,实际上是将变更加入Index(stage,
可以看作是提交集)
- svn rm 和 git rm
- svn mv 和 git mv
- svn commit 和 git commit 要注意的是 git commit 只提交 stage
中的内容(被 git add 命令记录的变更),git commit -a 可能才是 SVN 用户眼中的对应物
- svn blame 和 git blame
- svn merge 和 git merge svn merge 选择某个合并范围,则对应于 git
cherry-pick 命令
命令行不同,功能相似的命令
- svnadmin create 和 git init 用于初始化版本库的命令
- svn cat 和 git show
- svn log -v 和 git whatchanged
- svn cp trunk tags/… 和 git tag
- svn cp trunk branches/… 和 git branch
- svn switch 和 git checkout branchname
- svn resolve 和 git mergetool
- svn changelist 和 git add
互相找不到对应的命令
- svn cp 在 git 中没有对应物
- git rebase 在 svn 中没有对应物
- git push 在 svn 中没有对应物
- git reset 在 svn 中没有对应物
- git clean, stash, … 等命令在 svn 中没有对应物…
Subversion 用户眼中的 Git (4): 全局版本号和全球版本号
Subversion 的全局版本号和 CVS 的每个文件都独立维护一套版本号相比,是一个非常大的进步。在看似简单的全局版本号的背后,是
Subversion 提供对于事物处理的支持,每一个事物处理(即一次提交)都具有整个版本库全局唯一的版本号。
Git 的版本号则更进一步,版本号是全球唯一的。也可以说是全宇宙唯一的。 :-D Git 对于每一次提交,要将包括作者,提交内容等在内的信息整个作一个
SHA1 哈希,进而得到版本号。版本号得到一个 40 位十六进制字串。 什么?40位长的版本号?
全球唯一的版本号,您认为多长合适?
对于一个分布式的版本控制系统,每个人都可以独立创建提交,每次提交都形成一个新的版本号,每个版本号都不能够重复。使用摘要是解决唯一性的好方法,而当前来看使用
SHA1 是非常稳妥的。而 SHA1 就是一个 40位的十六进制的字串。 要是 SHA1 发生冲突了呢?即使真的发生冲突,还可以使用更加复杂的摘要算法,采用更长的摘要。如:SHA-224,
SHA-256, SHA-384, SHA-512, …
SVN 的版本号是连续的,可以预判下一个版本号,而 Git 的版本号则不是
因为 subversion 是集中式版本控制,当然很容易实现版本号的连续性。Git
是分布式的版本控制而且 Git 采用 40 位长的哈希值作为版本号,每个人的提交都是各自独立完成的,没有先后之分(即使提交有先后之分,也由于PUSH/PULL的方向和时机而不同)。
有人可能用过 Hg (Mercurial),可能会说 Hg 虽然也用 SHA1 摘要,但是还同时提供一个递增的数字版本号哇,为什么
Git 没有? 我是这么认为的:
- 首先 Hg 的递增数字版本号不是全局或者全球唯一的,而只是在本地版本中存在的一个指向全球版本号的别名而已;
- Hg 可以这么做,而 Git 没有这么做的原因是 Git 实现了真正的分支管理,而 Hg 版本库本身没有实现真正的分支管理;
Git 的版本号虽然不连续,但是是有线索的,即每一个版本都有对应的父版本(一个或者两个),进而可以形成一个复杂的提交链
Git 的版本号简化
如果每次需要引用使用版本号的时候,都使用 40 位的十六进制字符,那不是太麻烦了么?
Git 可以使用从左面开始任意长度的字串作为简化版本号,只要该简化的版本号不产生歧义。一般采用7位的短版本号。
Git 还可以使用 tag 来创建别名,即里程碑。
安全性,是 Git 版本号设计的另外一个考虑
和 subversion 不同,Git 版本库可以被任何人克隆,如果没有安全性的设计,克隆版本库后伪造提交数据再克隆给其他人,那么就是版本控制的灾难。
使用 SHA1 摘要作为版本号,同时解决了版本号唯一性和提交数据的安全性这两个问题。一单数据被伪造,将造成和
SHA1 摘要的版本号不符,引发异常。
Subversion 用户眼中的 Git (5): 没有部分检出
Subversion 可以将整个库检出到工作区,也可以将某个目录检出到工作区。对于要使用一个庞大、臃肿的版本库的用户,部分检出是非常方便和实际的。
但是 Git 只能全部检出,不支持按照目录进行部分检出。 那么这是为什么呢? —— Subversion
用户问道。 Git 的确没有部分检出,这并不是说只有将整个库克隆下来才能查看文件。有很多 git 工具,提供直接浏览git库的功能,例如
gitweb, trac 的 git 版本库浏览, redmine 的 git 版本库浏览。
Git 为什么没有部分检出?
Git 以及其他分布式版本控制工具,据我所知,都没有实现部分检出的功能,至少没有实现如下的部分检出:
想像中的完美版本控制系统(但并不存在):
- 可以对一个大的版本库(分布式)进行部分检出,检出的也是一个独立的小版本库(分布式)
- 小的版本库也可以被克隆
- 小的版本库中的提交可以 PUSH 到大的版本库中
- 小的版本库可以从大的版本库 PULL 相应目录的改动内容
这种版本控制系统不能在分布式版本控制系统中实现,我认为:
- 大的版本库和部分检出的小的版本库,无法保证提交ID的一致性 因为分布式版本控制系统的提交ID是整个提交信息的SHA1哈希值,大版本库的提交经过裁减——部分检出后,内容变了,提交
ID 也应该改变才对。
- 因为大的版本库和小的版本库(部分检出)的提交ID不一致,导致两个版本库的 PULL 和 PUSH
无法操作 即不知道哪些提交相互对应,造成两个版本库间的PULL和PUSH无法进行
- 分布式版本控制系统中,提交作为一个整体存在,一个提交由于部分检出而人为分拆,造成混乱,如何合并呢?
Git 为什么没有必要实现 svn export 的功能?
Subversion 有一条命令:svn export ,可以将 subversion
版本库的一个目录下所有内容导出到指定的目录下。而 git 却无法找到类似命令。为什么呢?
- Subversion 需要 svn export 命令是因为该命令可以导出一个干净的目录,即不包含
.svn 目录(包含配置文件和文件原始拷贝)
- Git 只在根目录存在一个 .git 目录,此外在各个子目录下不再有任何控制目录存在,因此无须通过另外的命令导出一个纯粹的干净的原始文件目录
- 只需要用系统提供的文件/目录拷贝命令,即可以实现将干净的目录复制到指定的目录中
Git-submodule 可以实现版本库的模块化
如果说 git submodule 和部分检出的相似性,还不如说 git
submodule 就是 svn:externals 翻版。 就是说 git submodule 可以将各个小的
git 版本库集合为一个大的版本库。如果不需要整个大的版本库的话,可以仅仅克隆某个小的 git 库。
Git-svn:集合 svn 的部分检出和 git 的便利
Git-svn 是 Subversion 的最佳伴侣,可以用 Git 来操作
Subversion 版本库。这带来一个非常有意思的副产品——部分检出:
- 可以用 git-svn 来对 Subversion 代码库的任何目录进行克隆,克隆出来的是一个git版本库
- 可以在部分克隆的版本库中用 Git 进行本地提交
- 部分克隆版本库中的本地提交可以提交到上游 Subversion 版本库的相应目录中
Subversion 用户眼中的 Git (6): stage
不单单是 Subversion 的用户,还包括其他类型的分布式版本控制的用户,如
Hg 的使用者,都可能会对 Git 的 stage 或称为 index 的东西感到非常的陌生。 但是一旦你熟悉
Git 的 stage 的秉性,你就会喜欢上它。
关于 stage ?概念
stage 是介于 workcopy 和 版本库 HEAD 版本的一种中间状态。通过索引文件
.git/index 可以找到 stage,因而 stage 有可以叫做 index。 stage 可以视作
Subversion 中的 changelist。即加入 stage 的变更在提交的时候提及到版本库,没有加入
stage 的变更则不会提交。Git 提供相应命令 (add,rm) 将工作区变更加入到 stage,也提供从
stage 中撤销变更的功能。
古怪的 git add
其他的版本控制系统,也提供 add 命令,但是 add 命令仅仅是将未标记为版本控制状态的文件标记为添加状态,并在下次提交时入库。
而 git 的 add 自命令,除了对尚未版本控制的文件进行添加外,还对工作区的修改文件进行操作。命令
git add 的含义是将工作区变更的文件或者新文件增加到版本库,并在下次提交的时候入库。 还好 git
add 不能用于对本地删除的文件进行操作,否则可真的是太怪了。 :-D 对于工作区直接删除的文件,需要用
git rm 命令进行标记,在下次提交时,在版本库中删除。
Stage 提供更好用的提交列表
Subversion 有提交列表功能,即将某些文件加入一个修改列表,提交可以只提交处于该列表的文件。但是
Subversion 的这个功能真的很少使用,因为:
- subversion 的changelist 增加了命令的复杂度
- 提交时如果忘了添加特定的参数,是对所有改动进行的修改,而非 changlist 中的改动
- 不同的changelist 的文件不能有交叉
Git 的 stage 却是一个你不得不用到的 changelist,让你在提交的时候明明白白的告诉
git 你要提交哪些改动。除非提交的时候使用 -a 参数(不建议使用)。 ?工作区的文件改动(新增文件,修改文件,删除文件),必须用
git add 或者 git rm 命令标识,使得改动进入 stage
- 提交只对加入 stage 的改动进行提交
- 如果一个文件改动加入 stage 后再次改动,则后续改动不改变 stage。即该文件的改动有两个状态,一个是标记到
stage 中并将在下次提交时入库的改动,另外的后续改动则不被提交,除非再次使用 git add 命令将改动加入到
stage 中
参数 –cached 和 git
当改动通过 git add/rm 添加到 stage 后,执行 git
diff 将看不到代码改动。这会让 git 新手大为疑惑。 实际上 git diff 是针对工作区和 stage
进行比较,如果改动已经加入到了 stage 中了,就不会在 git diff 中看到。 如果想查看加入到
stage 中的改动,到底更改了哪些地方,则使用 git diff --cached。即用 --cached
改变命令的比较对象,即比较 stage 和 HEAD 中的内容。
Subversion 用户眼中的 Git (7): 完全不同的分支和里程碑的实现
Subversion 曾经骄傲的宣称,自己的分支是轻量级的,眨眼之间分支立现。但是说实话,Subversion的分支和里程碑,是
svn copy 命令的副产品,好像是折衷的产物。 Git 分支一出,无人敢于争风,信乎?
Subversion 和 Git 的分支/里程碑都是轻量的
轻量级分支/里程碑的含义是,创建分支/里程碑的复杂度是 o(1),不会因为版本库的愈加庞大而变得缓慢。在
CVS 中,创建分支的复杂度是 o(n) 的,导致大的版本库的的分支创建非常缓慢。 Subversion
轻量级分支的实现是通过 svn cp 命令,即带历史的拷贝就是创建快速创建分支和里程碑的秘籍。 Git
的轻量级分支和里程碑就是全球唯一的提交号的别名,其中分支对应的是 git 树状提交的分支顶极节点。
Git 的分支是完全隔离的,而 Subversion 则没有
分支本来就应该是相对独立的命名空间,这在 Git 中是没有问题的,一个提交只能发生在唯一的一个分支中,虽然提交可以通过
cherry-pick 被“挑选”合并到其他分支。 Subversion 的分支相当于目录拷贝,约定俗成是拷贝在
branches/ 目录下,目录之间的隔离完全靠着使用者的自觉自愿,谁也不能阻止在一个提交中同时修改不同分支中的数据。
Git 的里程碑是只读的,而 Subversion 仅凭约定俗成的自觉自愿
里程碑是对某个历史提交所起的一个别名,作为历史的标记,是不应该被更改的。
Git 完全遵守历史不可更改这一时空法则。用户不能向 git 的里程碑中提交,否则里程碑就不是标记,而成了一个分支。当然
Git 允许用户删除里程碑再重新创建指定到不同历史提交。 Subversion 的里程碑和分支一样,都是用
svn cp 的带历史的拷贝创建的,作为一个子目录而存在。约定俗成,svn 的里程碑要建立到 tags/
目录下,要求不要在 tags/ 下的里程碑目录下进行提交。但是谁也阻止不了对未进行权限控制的里程碑的篡改。
Git 完备的里程碑和分支功能,另 Git 能完整克隆 SVN 版本库
很多分布式版本控制系统的分支功能是缺乏的,如 hg。因为像 hg/mercurial
这样的分布式版本控制系统,可以通过克隆来创建分支,即每个克隆都可以视为一个独立的分支。这就使得 hg 这样的缺乏分支的版本库很难完整的克隆一个
subversion 版本库。 Git 拥有完备的分支和里程碑功能,并且如前所述,其分支和里程碑管理比
SVN 还要优秀,这样就造就了一个工具 git-svn。工具 git-svn 实现了 git 对 svn
版本库的完整克隆,以及和 svn 的协同工作。
Subversion 用户眼中的 Git (8): SVN没有后悔药,git有好多
Subversion 没有后悔药,就是说一旦完成向服务器的数据提交,就没有办法再追回(从客户端),只能在后续的提交中修正——回退或者修改等。
Git 非常神奇,拥有无数颗粒后悔药… 为什么Subversion 没有后悔药,而 Git 拥有呢。因为
Subversion 作为集中式的版本控制,不能允许个人对已提交的数据进行篡改,而Git 是分布式版本控制系统,代码库是属于个人,允许任意修改。Git
通过对提交建立数字摘要来保证提交的唯一性和不可更改性,通过版本库在多人之间的多份拷贝保障数据的安全性。
Git 丢弃最新的一个或几个提交
使用 git reset –hard 命令可以永远丢弃最新的一个或者几个提交。
- 丢弃最新的一个提交: $ git reset --hard HEAD^
- 丢弃最新的两个提交: $ git reset --hard HEAD^^
- 丢弃某一提交之后的改动 $ git reset --hard COMMIT-ID
Git 和 Subversion 修改提交说明
提交后如果对提交说明不满意,Git 可以使用命令 git commit
–amend 修改提交说明。 Subversion 也可以修改提交说明,是通过修改提交的 svn:log
版本属性实现的:
- 不但可以修改最后一次提交的说明,并且可以修改历史提交的提交说明;
- Subversion 修改提交说明是不可逆的操作,可能会造成提交说明被恶意修改;
- Subversion 缺省关闭修改提交说明的功能。管理员在设置了提交说明更改的邮件通知后,才可以打开该功能
关于 Git 修改提交说明
- Git 可以修改最后一次提交说明,并不是说不能修改历史版本的提交说明,只是修改最后一个版本提交说明拥有最简单的命令
- Git 修改提交说明,会改变提交的 commit-id。即修改提交说明后,将产生一个新的提交
- Git 可以通过 git reset --hard ,git commit --amend,git
rebase onto 等命令来实现对历史提交的修改
- 使用 stg 工具可以更为简单的修改历史提交的提交说明,包括提交的内容
Git 可以修改和重构历史提交
使用 Git 本身的 reset 以及 rebase 命令可以修改或者重整/重构历史提交,非常灵活。
使用强大的 stg 可以使得历史提交的重构更为简洁,如果您对 stg 或者 Hg/MQ 熟悉的话。
Subversion 修改历史提交,只能由管理员完成
Subversion 是集中式版本控制系统,从客户端一旦完成提交,就没有办法从客户端撤销提交。但是管理员可以在服务器端完成提交的撤销和修改,但是操作过程和代价较大。
Subversion 对提交撤销和提交重构是通过 svnadmin dump, svndump filter,
svnadmin load 实现的版本库整理。
Subversion 用户眼中的 Git (9): 单亲 VS 多亲
SVN 和 GIT 对比的系列博文尚有几篇一直放在草稿中,处于构思阶段,今天从故纸堆里检出来(checkout?)
我们在《Subversion 用户眼中的 Git (7): 完全不同的分支和里程碑的实现》中介绍过,Git
和 Svn 的分支实现机制完全的不同,这也直接导致了 SVN 在分支合并中困难重重。尽管在 SVN 1.5
之后,通过 svn:mergeinfo 属性引入了合并追踪机制,但是在特定情况下,合并仍会出现很多困难。
在《SVN 树冲突和目录丢失问题》系列博文中,介绍了帮助我的一个朋友解决SVN树冲突的过程。这实际上在
GIT 中是 “a piece of cake”。你可以用 Git 模拟一下不同分支中文件目录改名引发合并冲突,在Git
中解决的是那么自然和漂亮! 这是为什么呢?因为 SVN 是单亲家庭,而 GIT 是双亲/多亲家庭啊。
SVN 的单亲家庭
在博文《Subversion 用户眼中的 Git (4): 全局版本号和全球版本号》中我们提到过,SVN
的版本号是连续的版本号。每一次新的提交都会版本号+1 ,而无论这个提交是在哪个分支中进行的。我们在《Subversion
用户眼中的 Git (7): 完全不同的分支和里程碑的实现》也提到过,SVN一个提交可以同时修改不同分支的不同文件,因为提交命令可以在
/trunk, /branches, /tags 的上一级目录执行。
- SVN 的提交是单线索的,每一个提交(最原始的提交0除外)都只有一个单亲节点(版本号小一个的提交节点)
- SVN 的提交链只有一条,仅从版本号和提交说明,我们无法获得分支图
- SVN 的分支图在某些工具(如乌龟SVN)可以提供,那是需要对提交内容进行检查,对目录拷贝动作视为分支,对
svn:mergeinfo 的改动视为合并,但这会由于目录管理的灵活性,导致千奇百怪的分支图表
- SVN 的单亲节点的设计,是SVN分支合并输在起跑线上的唯一原因。也是 SVN 永远无法在 GIT
面前抬头的最重要原因
Git 的双亲/多亲家庭
Git 的提交实际上是按照多亲进行设计的,即一个提交可以包含两个以上的多亲节点,不过一般合并是双分支合并,因此合并节点以双亲居多。
- 大部分提交实际上只用到了多亲节点中的一个,即大部分提交是对前一个提交的修改
- 合并操作一般用到两个双亲节点(或者更多),这很自然 合并操作是将两个提交合二为一,因此在新的合并结果的提交后产生的节点会有两个双亲节点。
Git 的提交也没有顺序的要求
- 不必非要基于最新节点提交,因为作为分布式版本控制系统,你根本不知道是不是有人在你之前进行和提交
- 你甚至可以先切换(检出)到之前老的提交节点,修改代码再进行提交,提交就形成分支
- 和其他人的版本库合并,也会形成分支
基于以上的特点,提交之间会形成复杂的提交家族谱系。从一个原始节点开始(甚至多个原始节点),派生出任意多的谱系。
- 可以用下面的命令查看提交关系图 ?最简单的命令是: $ git log --graph
- 还可以通过工具 gitk,qgit 等提供图形化的分支显示界面
- 下面是一个分支关系图示例。其中 A 为顶级节点(最新提交);B 节点是三个多亲节点的合并;后面类似公式一样的东西是
Git 节点的快捷操作符示例。
G H I J \ / \ / D E F \ | / \ \ | /
| \|/ | B C \ / \ / A A = = A^0 B = A^ = A^1 = A~1 C
= A^2 = A^2 D = A^^ = A^1^1 = A~2 E = B^2 = A^^2 F =
B^3 = A^^3 G = A^^^ = A^1^1^1 = A~3 H = D^2 = B^^2 =
A^^^2 = A~2^2 I = F^ = B^3^ = A^^3^ J = F^2 = B^3^2
= A^^3^2
附:相比 GIT的多亲节点,Hg/Mercurial 是按照双亲设计的,即在
Hg 中一次只能进行两个分支的合并。并且 Hg 提供 hg parents 命令查看节点的双亲。 预告一下,下一个对比的题目是:《Subversion
用户眼中的 Git —— 命令行的人性化设计》。 需要补充一句:抱歉关于版本控制的单亲和双亲的说法,可能会伤害到很多人,因为人类社会家庭分类中的相似概念。特别需要指出的是,这里的单亲双亲的概念和比较并不适合人类社会,因为对于人类社会的不适合的双亲关系,还不如单亲来得简单和直接。
Subversion 用户眼中的 Git (10): Git 命令行的人性化设计
Git 命令行的人性化设计?刚刚接触 Git 的 SVN 用户一定不予认同。
因为在 SVN 用户看来,co 必须严格写成 checkout, ci 必须严格写成 checkin,st
必须严格写成 status 的版本控制系统,怎么能说成人性化? 容我慢慢道来。
Git 命令别名简化命令输入
实际上 Git 可以通过配置别名,简化命令输入。例如 输入 st 相当于执行子命令
status,输入 ci 相当于 commit,输入 co 相当于 checkout,输入 br 相当于输入
branch,… 建立别名的命令:
$ git config --global alias.st status
$ git config --global alias.ci commit $ git config --global
alias.co checkout $ git config --global alias.br branch
$ git config --global alias.glog "log --graph"
如果您是管理员,还可以用 --system 替代 --global 命令,直接修改系统的配置文件
/etc/gitconfig,为系统所有登录用户配置 git 命令别名。
Git 命令提供自动分页功能
Git 几乎所有命令都提供分页器,即当命令输出超过一页时,自动在每页输出后暂停,可以按空格继续显示,按q退出,也支持其他
VIM 控制快捷键。 而在 SVN 中,你必须在命令后加上管道符以及 more 或者 less 来分页。
Git 输出的着色/语法加亮
Git 的命令输出很多都包含语法加亮,用不同的颜色标注不同含义的输出。例如
log 命令,diff 命令等。 而 SVN 缺省没有颜色显示,虽然也可以修改配置,以支持语法加亮。如在
/etc/subversion/config 文件中配置: diff-cmd = /usr/bin/colordiff
但是这么做,副作用也很大,会在 diff 导出成 patch 文件时,在 patch 中出现颜色控制字符。而且SVN语法加亮在
less 分页器启用后可能会失效。
Git 命令补全很强大
Git 命令行输入过程中,随时可以按 TAB 键补全命令或者其他参数,实现自动补全功能。例如在检出分支时,按下
TAB 能够自动补全分支名称或者显示分支名称列表。 当然 SVN 也有命令补全,也很不错。SVN 的分支补全不能用,则是因为
SVN 的分支实现太烂造成的。
Git commit --amend 功能很好用
提问:
- 如果提交后,觉得提交说明写的太烂,想重写怎么办?
- 如果提交后,发现有一个文件忘了提交,怎么办?
- 如果提交后,发现刚刚提交的代码有个拼写错误,怎么办?
回答:
- git ci --amend , 或者
- git ci --am<TAB> :-D
对于问题中的后两种情况,在执行 git ci --amend 之前,要执行
git add 将忘了提交的文件,或者修改后的文件添加的 Git 的 Index 中。
Git stash 命令很好用
Git stash 命令类似于我在写博客的草稿功能:可以把当前的修改进度保存为草稿,而并不影响文章发布的状态。
- 调用 git stash 将记录当前改动状态后,将工作区恢复到原始状态
- 我经常在切换分支时,通过该命令保留当前分支修改进度。因为 Git 不允许在当前分支处于修改状态时,切换到其他分支。
- 调用 git stash pop 命令,从草稿队列中调出最近一次压栈的修改。即上一次的修改又回来了。
|