本文参考文档如下:
五⼤场景玩转 Git,只要这一篇就够了!作者:孟宁 基于此文中的五大场景进行了探讨和实践
Pro Git 中文版 基于此文进行了基础理论知识的学习
Git 是一个开源的分布式版本控制系统,用于敏捷高效地处理任何或小或大的项目。最初是 Linus Torvalds 为了帮助管理 Linux 内核代码于2005年开发的一个版本控制软件,到了2008年随着Github网站的上线而为人所熟知。个人认为Git的特点就是:分布式、去中心化、开源。
下面谈谈Git的架构优势,分布式的架构相较于Svn等中心化的版本控制系统,主要差异在于Git在每一台终端都会维护各自的版本仓库,而不像中心化的版本控制系统那样终端只作为一个客户端进行简单的下载与提交,这样做的优点有二:1)提高容错率,系统发生单点故障时能够从其他终端的版本仓库中恢复 2)提高协作性,项目中各个小组可以在各自的本地仓库或者分支中使用各自的协作流程互不干扰,大幅度提高开发效率。
图1-1 分布式版本控制系统
Git核心概念
Git操作流程
Git的操作流程大致如下图:
图2-1 Git基本操作流程
对于本地 Repo如上图中间存储库Repository,实际上本地 Repo都存在项目根目录下.git文件夹中,内部可能有多个 branch,但至少有一个叫 master的branch。
本地Repo中的某个branch被checkout到当前workspace,就能在当前源代码目录中看到一份完整的源代码,这份完整的源代码就是workspace。
在workspace中新增文件或修改文件,只有完成add和commit两步操作才能将新增或修改的文件纳入本地Repo的存储库中进行版本管理。add和commit两步操作中间Index索引数据应该也是和本地 Repo一样存在项目根目录下.git目录中。
Git文件状态
对于每一个在Git系统管控下的文件,在Git中存在三种状态:已修改(modified),已暂存(staged)和已提交(committed)。已修改表示修改了某个文件,但还没有提交保存(在workspace中);已暂存表示把已修改的文件放在下次提交时要保存的清单(在index中);已提交表示该文件已经被安全地保存在本地数据库中了(在repository中);三个状态分别通过:1)本地修改 2)git add 3)git commit等操作逐级跃迁。
图2-2 Git文件状态以及对应的存放位置
Git 常用案例场景实践
下文列举了Git操作中的一些常用的场景,在各个操作中会注明操作的命令、截图,必要位置还会标注文件状态等,希望以后遗忘时便于复习参考。
Git 本地版本库操作
使用如下命令进行对本地仓库的初始化:
git init
后续的基本操作会在Git远程版本库中能够详细介绍。
Git 远程版本库操作
现在我们有远端仓库项目如图所示:
通过https协议的方式使用git命令拉取该项目到我们本地目录:
git clone https://github.com/fwv/Demo.git
可见现在已将远端仓库的内容拉到本地:
现在我们开始demo项目的开发工作,在demo目录下创建一个hi.txt里面内容为hello git,i am fwv!
使用如下命令将hi.txt文件提交到本地暂存区,使该文件由modified状态变为staged状态:
git add hi.txt
使用如下命令将hi.txt文件提交到本地仓库,使该文件由staged状态变为committed状态:
git commit -m "[add]create hi.txt file."
可以使用git status命令查看现在工作空间中已经没有东西可以提交,使用git log能够看到本次提交的记录:
最后一步使用如下命令将这次提交推送到远端,然后就能在远端仓库看到这一次的开发成果了!
git push
好了,到目前为止一次常规的在远程版本库上的开发就已经完成了。
Git 团队协作操作
Git 分支
什么是分支呢?在Git每一次commit的记录会形成一种链式的结构,每一个链节点把该次提交的全部内容的SHA-1 哈希字串计算校验和作为区分其他节点的标识,那么指向这些节点的指针就是分支,可以理解为master分支指向某一个版本的提交,而另一个分支指向更新或者更旧版本的提交。
那为什么需要分支呢?项目开发过程中,有的需求或者功能需要同时开发并且不能相互影响,如果在一个分支上开发的话首先会影响开发效率,其次功能1的开发过程中会混入很多功能2的提交记录,这不利于软件工程的开发思维,更有甚者还会影响代码调试,故而此时就需要分支功能的出现。
那我们继续上一小节的开发背景,在上一小节中我们在Demo项目中开发了hi.txt使用英文向Git进行了问候,那么现在有一个新需求,我们需要开发一个bonjour.txt使用法语向Git进行问候,该次开发需要在一个新分支b1上进行:
分支创建
git branch b1
git checkout b1
git chechout -b b1 //或者将两条命令结合起来
在b1分支上开发bonjour.txt文件并提交至远端:
可以看到远端仓库已经出现b1分支的开发了:
分支合并
分支b1的开发已经结束了,使用下列命令将改动合回master吧:
git checkout master
git merge b1
分支删除
可以看到b1分支上的开发内容已经顺利合回来了,那么现在使用下列命令卸磨杀驴删除掉b1分支吧:
git branch -d b1
好的,现在一个常见的分支开发已经结束了。
Git 冲突合并
什么是冲突?上一小节中记录的合并分支在实际开发情况中并不会总是这么顺利的发生,当两个分支都对同一个文件进行了修改那么在合并分支的时候就会出现冲突。冲突的出现在团队开发中是很常见的,当一个文件同时被多端修改的时候,那么代码合并时就一定会有冲突的出现。例如一个文件在同一条分支被两个人修改时,后提交的那个人就会出现冲突。或者一个文件在两条分支都被修改时,双边都能正常提交,但是当分支合并回主干时冲突也会登门拜访。
Git应对冲突的策略机制是什么?当冲突发生时,Git会在版本提交链中进行一次三方合并,会挑选出发生冲突的两个提交节点以及他们的共同父节点,然后在父节点的基础上合并冲突双方的数据,生成一个新的待提交节点,新节点把冲突双方节点都视作父节点,等待开发人员在新节点上手动处理完冲突以后作为一次新的提交,以此来达到解除冲突的效果。
书接上回,继续使用Demo项目的例子,我们在Demo项目上新拉一个分支b2,然后在master和b2分支上都对hi.txt文件进行各自的修改并提交,当分支合并的时候就出现了冲突:
冲突发生以后我们在master分支手动解决冲突,解决策略为两边都留下:
手动合并完以后作为一次新的提交,提交至远端仓库以后发现生成了新的提交节点:
好了,到这里一次常见的合并冲突就解决完毕了。
Git Rebase
Git Rebase是什么?Git提供两种方式来整合分支:merge(合并)与rebase(衍合),merge的机制在前面已经讲过,现在讲一讲rebase的机制,顾名思义rebase即重新选择当前版本的基础,在分支上执行rebase时候,git会先将本分支上的每一次提交都打包成一个个patch(补丁文件),然后以master分支的最新提交为新的基础,重演所有的patch,以达到合并的作用。重演时如果出现了冲突那么就需要解决冲突。举一个不太形象的例子,分支合并就好比帮派合并,merge方式就是两个帮派直接火拼,当场解决冲突以后合并成一个新的帮派;rebase方式就是分支帮派整理自己的所有过往,然后经过和谈以后在master帮派的基础上重演一遍分支帮派的建立过程,最终成为一个新的帮派。
Git Rebase的优势是什么? 1)便于整理提交日志,在项目开发的过程中往往会伴随着成百上千次的提交,那么这些提交记录就会堆积在git log中从而不方便查看阶段性的工作与成果,此时就需要git rebase提供的合并提交记录功能来使项目管理更加一目了然。2)简化合并工作,rebase方式在合并的过程中,维护者不需要做任何整合工作(译注:实际上是把解决分支补丁同最新主干代码之间冲突的责任,化转为由提交补丁的人来解决。),只需根据你提供的仓库地址作一次快进合并,或者直接采纳你提交的补丁。对应上述的例子,rebase方式的好处是:1)分支帮派的建立过程可能很漫长,整理以后可以变成一条 2)在master帮派基础上重演的时候难免发生很多冲突,而这个过程全程分支帮派自己处理而不用master帮派操心。
git rebase的常见使用方法如下:
git rebase -i [startpoint] [endpoint] //用于提交记录整理
git rebase [branch1] [branch2] //用于合并,在branch1的基础上冲那个眼branch2的提交
以下实践中会展示使用rebase来整理提交记录以及合并:
在Demo项目的基础上创建一个新分支b3,然后对hi.txt文件修改2次并提交:
使用以下命令开始进行记录整理:
git rebase -i head^^ //合并前2条提交记录
弹出vi编辑框,选择将第二条提交记录合并(squash)到第一条记录,保存退出:
编辑新提交的log信息:
合并成功以后发现前2条提交记录已经不见了,并生成一条新的合并提交记录:
好了,到这里我们在b3分支提交记录的合并整理已经完成了,下面开始使用rebase进行合并到master操作:
对hi.txt文件在b3分支上进行第三次修改并提交:
使用rebase以master为基础整合b3分支并解决冲突:
解决冲突以后此时b3已经是最新状态(包含了master分支上的最新内容)的版本了(这里注意rebase的所有冲突处理都由b3分支的管理者来进行,而不影响master):
切回master分支,直接接受b3的合并请求(注意此时两个分支之间必然不会出现冲突,因为b3的管理者已经处理好了,只需要简单接受即可完成合并):
好了,至此使用rebase来进行分支合并的实践就结束了,相比起merge的方式,rebase的方式真的非常优雅,总结起来就是我们在进行分支开发的时候,可以先将分支上面琐碎的提交记录整合成一个或者少个整洁的记录,然后在本分支上先整合好代码,处理好所有的冲突以后提交至主干的维护者进行简单的接受合并就好了。
使用git rebase时千万不能做的事情!!!
我们在使用git rebase的时候,一定一定要注意:不能对本分支上已经推送到远端的提交记录进行rebase操作!如果别人从远端更新到你已经提交的内容并且进行了自己的开发以后,你再把这条记录rebase的话,那么别人的工作在他再一次更新代码的时候就会出现混乱。
Git 撤回操作
当我们进行了一些错误的修改或者提交以后应该如何撤销和回滚呢?
还没有add时
git checkout -- filename //撤销指定文件
git checkout -- . //撤销全部文件
已经add但是没有commit时
git reset HEAD filename
git reset HEAD .
已经commit但是没有push时
git reset --hard <commit_id>
git reset --hard HEAD^* //^长度可变,大于等于1
已经push
git revert <commit_id>
Github 开源项目操作(Fork、Pull)
在对一些开源项目进行自己的贡献时,先点击项目右上角fork到自己仓库:
在本地进行好修改以后想要合并回该项目时就发起pull请求,项目的维护人就会在pull请求的页面中收到你的请求了: