整理下git中不是很清晰的一些指令的背后作了什么,有说的不对的地方,欢迎指正。html
这节缘起于最近给公司的优秀的开源组件库提pr,在开发说明文档中看到,推荐在提交pr前建议用git rebase 清理下commit再提pr~。恩,平时合并改动的时候git merge是老朋友,经常使用到,介个git rebase有据说,可是用的很少。特此了解下二者的区别,简单记录下吧。git
首先git merge 和 git rebase 均可以合并两个分支的commit。缓存
举个栗子:spa
git checkout feature
git merge master
复制代码
feature上除了合并了master分支上改动的commit外会多一个merge的commit说明此次merge,此外不会生成新的commit。3d
举个栗子:指针
git checkout feature
git rebase master
复制代码
feature上的新的commit会所有从新以master最新的commit为base,效果就是feature上的新的commit就像是在最新的master上checkout出来的分支上后续添加的commit,这些commit是新生成的。而且不会有那个专门说明合并的commit。【有些人会以为这种commit污染了commit的记录~,可是也有人以为这个merge commit能够帮忙理解到每次合并操做的发生的时间和合并的分支,这些信息是有用的。恩,我都好~code
引用很漂亮的图来解释以下【图来自《Merging vs. Rebasing》】:cdn
划重点,其实须要注意的点在于:htm
这有个问题,举个栗子:blog
就是git rebase的feature分支若是是一个多人开发的分支,那么rebase的是你本地的分支,合做的小伙伴的分支和你的这个分支进行同步的话,会有一些内容重复的可是commit的hash值不同的多余的commit,整个commit就冗余不干净了~
所以比较好的使用git rebase的场景是单人开发的分支,或者使用一个临时的分支进行git rebase 而后使用git merge到master分支上,merge master的时候是fast forward的,而且不会有master到feature的merge commit。
此外
在知道了这些重要的点后,就能够自行判断什么时候应该使用git merge 仍是 git rebase。git rebase还有不少用法,这里不详细说明,要用时再了解就行了。
【本节插图来自Resetting, Checking Out & Reverting】
这三个指令均可以作到撤销改动的做用,可是背后的行为是不同的,结果也有所不一样,其中git checkout确定是你们最熟悉的。这里分别按顺序介绍下三个指令的做用以及背后作了什么。
分别称为:
这三个区分别管理着git项目中的改动的不一样阶段的状态。
git checkout是工做中常见的一个操做。
在对commit操做的时候,简单来讲就是将HEAD(HEAD个人理解是一个指向当前活跃状态或者说当前活跃commit的一个指针,表示的是如今作出的版本状态)移动到对应的commit,当你checkout一个分支的时候,则是将HEAD指针移至这个分支的最新的一个commit。git checkout并不会影响分支上的commit,而只是切到对应的commit的版本状态,在切换以前,须要保存当前的改动而且commit,由于你一旦切走了,虽然没有改动commit的历史,以后也能够切回来,可是checkout走了以后,你的HEAD就不指向当前分支最新的状态了,这时候须要将改动保存,以后最为一个commit版原本进行管理。用图来表示以下:
只是改变了HEAD的指向,并无改动到commit的历史,这个时候工做区和暂存区的状态保持一致为checkout到的这个版本的状态。
也就是说,git checkout对commit操做的时候的做用为查看历史版本,固然你也能够在这时候进行改动而且commit,这样操做的结果就是,在checkout到的commit的基础上多了一个没有归属任何分支的commit。当你这时checkout回其余分支的时候,git会提醒你给这个刚才在'detached HEAD' state时新增的commit的那个改动的分叉建立一个分支,这样方便以后切到这个状态,而不是一个没有branch归属的commit改动。
git checkout对文件进行操做的时候,则是将工做区的指定的文件或者目录的状态切换成指定的版本的状态,对暂存区和版本区没有影响。若是你git checkout一个文件的时候默认是HEAD,产生的效果就是放弃工做区当前的改动,这个也是小伙伴们使用git checkout比较多的操做之一。
git revert只能操做commit,不能对文件进行操做,git revert的撤销背后的原理,就是用一次新的反向的commit将工做区、暂存区、版本区的状态所有回退到指定的版本。也就是你的commit历史会多一个commit,这个commit的操做就是指定的那些撤销改动。git revert不会对历史的commit进行改动,所以经常使用与公共分支的回滚。保留了commit历史,同时也完成了版本回滚,而且其余小伙伴在同步此次的回滚的时候只是至关于fast forward了一个commit版本。用图来表示以下:
git reset这个操做能够对文件也能够对commit进行操做,git reset须要慎用,由于git reset是会改动到commit的历史的。
git reset指定文件的时候会将缓存区同步到你指定的那个提交。git reset默认reset到HEAD,因此能够用来移除暂存区的指定文件。文件层面只支持--mixed参数,做用就是unstaged对应的暂存区中的文件。
git reset的撤销操做是“真 · 撤销”操做,git reset 将一个分支的末端指向另外一个提交。这能够用来移除当前分支的一些提交。被移除的commit在下次 git 执行垃圾回收的时候会被删除。换句话说,若是你想完全的扔掉提交,你能够这么作。用图来表示以下:
git reset会改写当前分支的commit的历史,因此最好不要在公共分支上进行这个操做。git reset 操做有三个选项来指定这个操做的影响范围或者说做用域:
《git reset soft,hard,mixed之区别深解》中的解释,我以为挺清晰的,参考总结以下:
--soft参数告诉Git重置HEAD到另一个commit,但也到此为止。全部的在original HEAD和你重置到的那个commit之间的全部变动集都放在暂存区中。
--hard参数将会blow out everything.它将重置HEAD返回到另一个commit,重置暂存区以便反映版本区的变化,而且重置工做区也使得其彻底匹配起来。这是一个比较危险的动做,具备破坏性,数据所以可能会丢失(makes everything matching the commit you have reset to.)。若是真是发生了数据丢失又但愿找回来,那么只有使用:git reflog命令了。
--mixed是reset的默认参数,也就是当你不指定任何参数时的参数。它将重置HEAD到另一个commit,而且重置暂存区以便和版本区相匹配,可是也到此为止。工做区不会被更改,全部该branch上从original HEAD(commit)到你重置到的那个commit之间的全部变动将做为local modifications保存在工做区中,(被标示为local modification or untracked via git status),可是并未staged的状态,你能够从新检视而后再作修改和commit。
【图表来自参考资料文章】
在合并分支的时候git merge是老朋友,通常的小伙伴都是直接git merge 巴拉巴拉吧就解决了,这种状况下默认使用的是fast forward模式进行分支的合并。另外咱们能够在git merge的时候加上--no-ff参数来切换成non fast forward模式进行分支的合并。这两种合并方式的结果不一样,适用于不一样的场景。
fast forward简单来讲,就是将分支改动的commit按照时间顺序依次并入到(举个栗子)master分支上,合并的分支和master分支是一个扁平的关系。这里有个问题就是,commit可能和master上新增的commit穿插到一块儿,而且,在branch tree上,没办法直观的看到一个分支的全部改动都有哪一些,由于这些改动被插入到master的commit历史中了。
non fast forward简单来讲,合并分支的时候必定会多产生一个merge commit来讲明此次合并的操做。而且在branch tree上被合并的分支和master分支的关系不是扁平的,是能够清晰的看到这个合并的操做合并了哪些commit。此外,non fast forward的合并的回滚也比较方便,只要revert到对应的这个merge commit就行了。不会有fast forwar的模式下回滚的时候影响的commit穿插在其余正常改动的commit中间,这时候,回滚就很难办,可能会回滚掉正常的commit,在公共开发的分支上进行rebase整理commit历史也很差(缘由在git rebase中已经说明了),整个处理起来就比较麻烦。
用图来解释以下:
参考资料: