读了“扔物线”老师的小册《Git 原理详解及实用指南》感受收获良多,因而想写点东西作一个总结,即加深本身的印象也但愿能给社区小伙伴一点帮助,写的不对的地方还请多多指导。身为一个初入前端半年的菜鸟,由伊始的只知道git是用来托管代码的工具到逐步了解中央版本控制系统与分布式版本控制系统(git)的原理与区别;从以前只会基本的add、commit、pull、push操做到使用stash、merge、reset方便得不亦乐乎,都得益于对git原理的深刻理解,逼话少说,咋们直接进入正题。前方长篇预警...前端
所谓版本控制,就是在文件修改的历程中保留修改历史,能够方便的撤销(如同文本编辑的撤销操做通常,只是版本控制会复杂的多)以前对文件的修改。一个版本控制系统的三个核心内容:版本控制(最基本的功能),主动提交(commit历史)和远程仓库(协同开发)。git
工做模型安全
- 主工程师搭好项目框架
- 在公司服务器建立一个远程仓库,并提交代码
- 其余人拉取代码,并行开发
- 每一个人独立负责一个功能,开发完成提交代码
- 其余人随时拉取代码,保持同步
分布式与中央式的区别主要在于,分布式除了远程仓库以外团队中每个成员的机器上都有一份本地仓库,每一个人在本身的机器上就能够进行提交代码,查看版本,切换分支等操做而不须要彻底依赖网络环境。
工做模型服务器
- 主工程师搭好项目框架 ,并提交代码到本地仓库
- 在公司服务器建立一个远程仓库,并将1的提交推送到远程仓库
- 其余人把远程仓库全部内容克隆到本地,拥有了各自的本地仓库,开始并行开发
- 每一个人独立负责一个功能,能够把每个小改动提交到本地(因为本地提交无需当即上传到远程仓库,因此每一步提交没必要是一个完整功能,而能够是功能中的一个步骤或块)
- 功能开发完毕,将和这个功能相关的全部提交从本地推送到远程仓库
- 每次当有人把新的提交推送到远程仓库的时候,其余人就能够选择把这些提交同步到本身的机器上,并把它们和本身的本地代码合并
假设你已经安装好了git并将代码clone到了本地,新手移步git安装与代码拷贝指南。markdown
首先理解三个基本概念:
网络
stage 这个词在 Git 里,是「集中收集改动以待提交」的意思;而 staging area ,就是一个「聚集待提交的文件改动的地方」。简称「暂存」和「暂存区」。至于 staged 表示「已暂存」,就不用再解释了吧?3.如今文件已经放入暂存区,能够用commit命令提交:
git log
查看提交历史)
git status
能够看到,该文件 又变红了,不过此次它左边的文字不是 "New file:" 而是 "modified:",并且上方显示它的状态也不是 "Untracked" 而是 "not staged for commit",意思很明确:Git 已经认识这个文件了,它不是个新文件,但它有了一些改动。因此虽然状态的显示有点不一样,但处理方式仍是同样的:
工做模型
1.在上面基本操做的基础上,同事 commit 代码到他的本地,并 push 到远程仓库
2.你把远程仓库新的提交经过 pull指令拉取到你的本地
经过这个流程,你和同事就能够简单地合做了:你写了代码,commit,push 到远程仓库,而后他 pull 到他的本地;他再写代码,commit, push 到远程仓库,而后你再 pull 到你的本地。你来我往,配合得不亦乐乎。(可是有时候push会失败)app
为何会失败?
由于 Git 的push 实际上是用本地仓库的commit记录去覆盖远程仓库的commit记录(注:这是简化概念后的说法,push 的实质和这个说法略有不一样),而若是在远程仓库含有本地没有的commit的时候,push (若是成功)将会致使远端的commit被擦掉。这种结果固然是不可行的,所以 Git 会在 push 的时候进行检查,若是出现这样的状况,push 就会失败框架
这时只须要先经过git pull
(实为fetch和merge的组合操做)将本地仓库的提交和远程仓库的提交进行合并,而后再push就能够了分布式
核心:
(1)任何新的功能(feature)或 bug 修复全都新建一个 branch 来写;
(2)branch 写完后,合并到 master,而后删掉这个 branch(可以使用git origin -d 分支名
删除远程仓库的分支)。 工具
当前 commit 在哪里,HEAD 就在哪里,这是一个永远自动指向当前 commit 的引用,因此你永远能够用 HEAD 来操做当前 commit,
HEAD 是 Git 中一个独特的引用,它是惟一的。而除了 HEAD 以外,Git 还有一种引用,叫作 branch(分支)。HEAD 除了能够指向 commit,还能够指向一个branch,当指向一个branch时,HEAD会经过branch间接指向当前commit,HEAD移动会带着branch一块儿移动:
branch 包含了从初始 commit 到它的全部路径,而不是一条路径。而且,这些路径之间也是彼此平等的。
git branch 名称
git checkout 名称
(将HEAD指向该branch)
git checkout -b 名称
git branch -d 名称
所谓引用,其实就是一个个的字符串。这个字符串能够是一个 commit 的 SHA-1 码(例:c08de9a4d8771144cd23986f9f76c4ed729e69b0),也能够是一个 branch(例:ref: refs/heads/feature3)。
Git 中的 HEAD 和每个 branch 以及其余的引用,都是以文本文件的形式存储在本地仓库 .git 目录中,而 Git 在工做的时候,就是经过这些文本文件的内容来判断这些所谓的「引用」是指向谁的。
(1)把当前branch位置上传到远程仓库,并把它路径上的commits一并上传
(2)git中(2.0及之后版本),git push
不加参数只能上传到从远程仓库clone或者pull下来的分支,如需push在本地建立的分支则需使用git push origin 分支名
的命令
(3)远端仓库的HEAD并不随push与本地一致,远端仓库HEAD永远指向默认分支(master),并随之移动(可使用git br -r
查看远程分支的HEAD指向)。
含义:从目标 commit 和当前 commit (即 HEAD 所指向的 commit)分叉的位置起,把目标 commit 的路径上的全部 commit 的内容一并应用到当前 commit,而后自动生成一个新的 commit。
git merge branch1
操做,Git 会把 5 和 6 这两个 commit 的内容一并应用到 4 上,而后生成一个新的提交 7 。
git merge --abort
放弃解决冲突,取消merge
有些人不喜欢 merge,由于在 merge 以后,commit 历史就会出现分叉,这种分叉再汇合的结构会让有些人以为混乱而难以管理。若是你不但愿 commit 历史出现分叉,能够用 rebase 来代替 merge。
为何要从 branch1 来 rebase,而后再切回 master 再 merge 一下这么麻烦,而不是直接在 master 上执行 rebase?
从图中能够看出,rebase 后的每一个 commit 虽然内容和 rebase 以前相同,但它们已是不一样的 commit 了(每一个commit有惟一标志)。若是直接从 master 执行 rebase 的话,就会是下面这样:这就致使 master 上以前的两个最新 commit (3和4)被剔除了。若是这两个 commit 以前已经在远程仓库存在,这就会致使无法 push : ![]()
因此,为了不和远程仓库发生冲突,通常不要从 master 向其余 branch 执行 rebase 操做。而若是是 master 之外的 branch 之间的 rebase(好比 branch1 和 branch2 之间),就没必要这么多费一步,直接 rebase 就好。 ![]()
须要说明的是,rebase 是站在须要被 rebase 的 commit 上进行操做,这点和 merge 是不一样的。
stash 指令能够帮你把工做目录的内容所有放在你本地的一个独立的地方,它不会被提交,也不会被删除,你把东西放起来以后就能够去作你的临时工做了,作完之后再来取走,就能够继续以前手头的事了。
操做步骤:
(1)git stash
能够加上save参数后面带备注信息(git stash save '备注信息'
)
(2)此时工做目录已经清空,能够切换到其余分支干其余事情了
(3)git stash pop
弹出第一个stash(该stash从历史stash中移除);或者使用git stash apply
达到相同的效果(该stash仍存在stash list中),同时可使用git stash list
查看stash历史记录并在apply后面加上指定的stash返回到该stash。
注意:没有被track的文件会被git忽略而不被stash,若是想一块儿stash,加上-u参数。
能够查看git的引用记录,不指定参数,默认显示HEAD的引用记录;若是不当心把分支删掉了,可使用该命令查看引用记录,而后使用checkout切到该记录处重建分支便可。
注意:再也不被引用直接或间接指向的 commits 会在必定时间后被 Git 回收,因此使用 reflog 来找回被删除的 branch 的操做必定要及时,否则有可能会因为 commit 被回收而再也找不回来。
git log -p
能够查看每一个commit的改动细节(到改动文件的每一行)
git log --stat
查看简要统计(哪几个文件改动了)
git show 指定commit 指定文件名
查看指定commit的指定文件改动细节
git diff --staged
能够显示暂存区和上一条提交之间的不一样。换句话说,这条指令可让你看到「若是你当即输入 git commit,你将会提交什么」
git diff
能够显示工做目录和暂存区之间的不一样。换句话说,这条指令可让你看到「若是你如今把全部文件都 add,你会向暂存区中增长哪些内容」
git diff HEAD
能够显示工做目录和上一条提交之间的不一样,它是上面这两者的内容相加。换句话说,这条指令可让你看到「若是你如今把全部文件都 add 而后 git commit,你将会提交什么」(不过须要注意,没有被 Git 记录在案的文件(即历来没有被 add 过的文件,untracked files 并不会显示出来。由于对 Git 来讲它并不存在)实质上,若是你把 HEAD 换成别的commit,也能够显示当前工做目录和这条 commit 的区别。
再提一个修复了错误的commit?能够是能够,不过还有一个更加优雅和简单的解决方法:commit --amend。
具体作法:
(1)修改好问题
(2)将修改add到暂存区
(3)使用git commit --amend
提交修改,结果以下图:
使用rebase -i(交互式rebase):
所谓「交互式 rebase」,就是在 rebase 的操做执行以前,你能够指定要 rebase 的 commit 链中的每个 commit 是否须要进一步修改,那么你就能够利用这个特色,进行一次「原地 rebase」。
操做过程:
(1)git rebase -i HEAD^^
说明:在 Git 中,有两个「偏移符号」: ^ 和 ~。
^ 的用法:在 commit 的后面加一个或多个 ^ 号,能够把 commit 往回偏移,偏移的数量是 ^ 的数量。例如:master^ 表示 master 指向的 commit 以前的那个 commit; HEAD^^ 表示 HEAD 所指向的 commit 往前数两个 commit。
~ 的用法:在 commit 的后面加上 ~ 号和一个数,能够把 commit 往回偏移,偏移的数量是 ~ 号后面的数。例如:HEAD~5 表示 HEAD 指向的 commit往前数 5 个 commit。
上面这行代码表示,把当前 commit ( HEAD 所指向的 commit) rebase 到 HEAD 以前 2 个的 commit 上:
git rebase --continue
继续 rebase 过程,把后面的 commit 直接应用上去,此次交互式 rebase 的过程就完美结束了,你的那个倒数第二个写错的 commit 就也被修正了:
git reset --hard HEAD^
HEAD^ 表示 HEAD 往回数一个位置的 commit ,上节刚说过,记得吧?
操做步骤与修改历史提交相似,第二步把须要撤销的commit修改成drop,其余步骤再也不赘述。
git rebase --onto HEAD^^ HEAD^ branch1
上面这行代码的意思是:以倒数第二个 commit 为起点(起点不包含在 rebase 序列里),branch1 为终点,rebase 到倒数第三个 commit 上。
有的时候,代码 push 到了远程仓库,才发现有个 commit 写错了。这种问题的处理分两种状况:
假如是某个你本身独立开发的 branch 出错了,不会影响到其余人,那不要紧用前面几节讲的方法把写错的 commit 修改或者删除掉,而后再 push 上去就行了。可是此时会push报错,由于远程仓库包含本地没有的 commits(在本地已经被替换或被删除了),此时直接使用git push origin 分支名 -f
强制push。
(1)增长新提交覆盖以前内容
(2)使用git revert 指定commit
它的用法很简单,你但愿撤销哪一个 commit,就把它填在后面。如:git revert HEAD^
上面这行代码就会增长一条新的 commit,它的内容和倒数第二个 commit 是相反的,从而和倒数第二个 commit 相互抵消,达到撤销的效果。在 revert 完成以后,把新的 commit 再 push 上去,这个 commit 的内容就被撤销了。它和前面所介绍的撤销方式相比,最主要的区别是,此次改动只是被「反转」了,并无在历史中消失掉,你的历史中会存在两条 commit :一个原始 commit ,一个对它的反转 commit。
git reset --hard 指定commit
你的工做目录里的内容会被彻底重置为和指定commit位置相同的内容。换句话说,就是你的未提交的修改会被所有擦掉。
git reset --soft 指定commit
会在重置 HEAD 和 branch 时,保留工做目录和暂存区中的内容,并把重置 HEAD 所带来的新的差别放进暂存区。
什么是「重置 HEAD 所带来的新的差别」?就是这里:
git reset --mixed(或者不加参数) 指定commit
保留工做目录,而且清空暂存区。也就是说,工做目录的修改、暂存区的内容以及由 reset 所致使的新的文件差别,都会被放进工做区。简而言之,就是「把全部差别都混合(mixed)放在工做区中」。
checkout的本质是签出指定的commit,不止能够切换branch还能够指定commit做为参数,把HEAD移动到指定的commit上;与reset的区别在于只移动HEAD不改变绑定的branch;git checkout --detach
能够把 HEAD 和 branch 脱离,直接指向当前 commit。
但愿个人总结能给你们带来些许帮助,也但愿和你们一块儿学以至用,一块儿成长。最后,万分感谢扔老师的小册,强势安利《git原理详解与实用指南》,认准扔物线。