Git简介
在我们的认知范围Git和SVN都是对于代码托管的工具,那么这两者又有什么不同呢?
Git是世界上先进的「分布式的版本控制系统」,而SVN是「集中式的版本控制系统」,SVN对于版本的管理集中于中央服务器中,而Git对于版本的管理可以在本地。
SVN管理的模式从SVN服务器中拉取代码,然后开始自己的开发,开发完后再向SVN服务器提交代码,所以集中式的版本管理,需要联网才能进行,一旦没网就没办法向SVN服务器提交代码。
而Git是分布式的版本管理,每个开发者的本地都会有完整的版本库,不需要来联网,也能进行版本的管理和代码的提交,每个开发者都可以再本地进行提交代码、查看版本、切换分支等操作。
所以相对于SVN来说Git的存储也会相对比较占用空间,但是以空间换来了Git对版本管理的高效,不得不说是一种高明的策略。
Git安装
Git可以安装在Windows或者Linux,安装在Windows相信大家都会,基本就是下载软件,然后傻瓜式操作,再Windows安装后,就会有Git GUI Here以及Git Bash Here。
Git Bash Here就是我们用来敲命令的窗口,打开它就可以敲关于Git的命令进行进行操作。
Windows的Git下载地址:https://git-scm.com/downloads,在这里下载最新版的进行安装即可。
下面我们来说一下Git再Linux的安装过程,要在安装Git其实也非常简单,可以直接使用yum源进行安装,一句命令就搞定了:
Git的配置
安装完Git后就开始对Git进行配置操作,配置自己用户名和Email,配置的命令如下:
配置完信息后,就可以「创建目录,并且初始化自己的本地仓库」了:
我这里已经初始化过了,初始化后会默认在主干上(master),这里为了测试各种Git的各种命令使用本地的Git仓库于github进行关联。
本地仓库与Github关联
在你的c盘下面有一个.ssh文件夹,进入文件夹里面可以看到有id_rsa.pub和id_rsa两个文件,第一个文件是id_rsa.pub里面的信息是公钥,而第二个文件是私钥。
加入没有这两个文件,可以使用一下命令进行生成:
接着就是把自己的公钥复制粘贴配置到Github上的SSH Keys页面中,快捷地址:https://github.com/settings/ssh ,
在Github上配置完自己的公钥后,就可以在Github中创建仓库进行测试,在Github的右上角中找到:create a new repo,创建一个新的仓库:
这样就简单的创建自己的Github的仓库了,创建完后就可以把自己的本地仓库文件同步到GitHub中,使用一下命令:
这样你本地的Reids目录下的文件与Github进行了关联,只要在Redis目录中修改了文件,就可以使用git push origin master推向远程的Github仓库。
这有一点说明的就是这里配置的是https的方式,可以配置成ssh的方式,因为http上的方式每次推向远程仓库的时候都会让你输入密码,有点麻烦:
切换的方法,如下图所示,只要跟着下面的命令进行操作就能随意进行协议的切换了,还是比较简单的,这里就直接略过:
Git原理
上面说了那么多就是简简单单的对Git进行介绍,做一个简单的入门,下面就开始Git的原理的深入的剖析。
在Git中有四个概念:「远程仓库、工作区、暂存区、版本库」。远程仓库就是我们Git的服务器,用于存储已经管理团队的代码。
工作区、暂存区、版本库是我们本地的,例如当我们初始化git init后,就会在当前的目录下出现.git目录,「redis目录就是我们的工作区,而.git目录是我们的版本库所有的版本信息都在这里」。
在.git目录下index文件(.git/index),这就是「暂存区」,叫做stage或者index,index和我们的数据库的index类似,所以我们有时候也叫它为「索引」。
这四个区域实现的原理图所下所示,使用过Git的对于下面的命令再熟悉不过了。
从原理图中可以看出代码可以再不同的level之间转移,也可以跨level之间转移,所有的这些动作都是通过Git的命令去实现。
初始化的时候Git还会自动为我们创建第一个分支master,以及指向master的一个指针叫做HEAD。
克隆项目
在我们实际的工作环境中,都会从服务器上进行克隆项目到本地,Git中使用git clone命令可以进行克隆项目:
执行git clone就会生成一份副本,在本地仓库和工作区都会同步副本,具体的原理图如下所示:
提交代码
从上面的图中我们可以到,代码可以在不同level之间移动,高level到低level,或者逆向低level到高level,也可以跨level之间移动。
Git中代码从低level到高leve的移动主要依靠以下命令:
git add .:文件添加进暂存区。git commit -m “提交信息”:文件添加进本地仓库,-m参数改为-am可以直接推向本地仓库。git push:文件推向远程仓库。运行git commit -a相当于运行git add把所有文件加入暂存区,然后再运行git commit把文件提交本地仓库。
代码回退
那么从高level向低level移动代码的命令如下:
git pull:从远程仓库拉取代码到本地。git reset –files:用本地仓库覆盖暂存区中修改,也就是覆盖最后一次git add的内容。git checkout –files:把文件从暂存区复制到工作区,用于放弃本地的修改。git checkout HEAD –files:回退最后一次的提交内容。下面我用自己本地与github的操作测试上面的命令,加深对上面的命令的理解和使用,当我在本地新建一个github仓库中没有的文件:
可以看到文件的显示Untracked files:未被追踪的文件,「表示该文件未被git追踪管理」。
新添加的文件可以通过「git add添加到在暂存区」,「这样文件就能够被git进行追踪」,此时再使用git status查看文件时,就可以看到两个文件已经是以new file的形式进行显示:
版本回退
若是你想撤销提交到暂存区的内容,使用git reset,可以撤销向暂存区新添加的文件:
也可以在使用命令:git reset –hard HEAD^,表示回退上一个版本,「在Git中HEAD表示当前版本,HEAD^表示上一个版本」,若是有多个版本,这样表示就不方便了,可以使用HEAD~10,表示版本的次数。
在Git每一个commit都会有自己的commit的ID,可以通过git log进行查看:
commit的本质就是:「每次Git都会用暂存区的文件创建一个新的提交,把当前的分支指向新的提交节点,这样就完成了一次新的提交」:
若是HEAD指针指向的是bran分支,那么新的节点就会成为jh509的子节点,并且形成新的分支:
也就可以使用git log –pretty=oneline:直接输出commit的ID,信息比较简短,然后直接指定ID的回退:
当你再次检查你的代码的时候就会回到了id为5567a版本,在Git的版本回退原理中,Git的内部有一个指当当前版本的HEAD指针,只要从当前版本指回去就行了,所以Git版本的回退是特别快的,只需要移动指针,实现的原理图如下所示:
撤销修改
丢弃工作区的修改使用:git checkout — file命令,这条命令中的–files是不能漏的,若是只是git checkout就表示切换另一条分支的命令了。
在我的本地我直接修改:README.md文件,然后使用git status进行查看,他表示文件处于modified状态:
此时的README.md文件是还没有被添加进暂存区的,可以直接使用以下命令,撤销掉工作区的修改:
若是已经添加到暂存区了,就是用以下的命令进行回撤:
上面也演示了git reset命令,它既可以回退版本,又可以把暂存区的修改回退到工作区。当我们用HEAD时,表示最新的版本。
当你提交了修改后,可以使用git diff查看两次提交之间的变动,它的本质就是「任意比较两个仓库之间的差异」:
删除文件
在工作区直接使用rm fileName,这个操作和linux的命令一样,若是文件已经提交版本库,从版本库中删除文件可以使用git rm命令进行删除,然后提交:
若是删除错了,可以使用git checkout — README.md进行恢复,其原理就是使用版本库的文件替换工作区的文件。
代码冲突
在团队中集体使用Git的时候,每个人都提交自己的代码最后合并到主干,总有会push失败的时候,因为push的本质:「就是用你本地仓库的commit记录去覆盖远程仓库的commit记录」。
但是别人提交了一些代码,而你本地并没有这些代码,这样代码就会被覆盖,导致别人的commit的记录就不存在,这个是绝对不允许的。
所以,每次push的时候Git就会检查,若是存在这种情况就是push失败,只要先git pull一下,将本地仓库与远程仓库先合并一下,最后push就可以成功了,若是文件中已经存在在冲突代码,只要打开文件重新解决一下冲突即可。
分支管理
Git中比较最重要的一点就是分支的概念,有了分支就有了合并和衍合的操作,「合并」和「衍合」能够「有效的对代码版本的管理」。
Git的初始化中有一条默认的主分支叫做master,每一次的提交都会串成一条时间线,这就是一条分支,当前分支由HEAD指针指向:
当每次发生代码提交的时候,当前指向就会向前形成一个新的版本,假如再创建一个新的分支bran,并且当前的提交指向新的分支,这样新的分支随着时间的推移就会形成许多版本:
当新分支开发完后,提交仓库,并合并到主干master,最后删除bran分支,这样就完成了一次个人的开发:
所以,假如主分支上只建立一条分支的话,分支的合并是非常快速的,只需要移动master分支到当前提交,然后将HEAD指针指向master,最后删除bran分支就完成了。
但是,事实上并不是这样的,在一个多人协作的开发团队中,往往每个人都会建立自己的分支,有自己的提交,最后合并到主干,当自己提交的时候,远程仓库代码就会存在自己本地仓库并未有的代码,这样就会导致push失败。
例如:程序员Tom和Jerry同时迁出代码,他们的初始代码分支都如下图所示:
当Tom开发自己的业务模块,提交代码并且合并到主干后,远程主干分支如下图所示:
「远程仓库master已经不再指向gs234,而是新生成了一个版本dfd453,作为当前指向的版本」。
与此同时,Jerry的本地也同时开发完自己的模块后,分支如下图所示:
在Jerry的本地环境中,他的「本地仓库master还是指向gs234」,Jerry在自己新建立的一个分支bran中进行开发,开发完后合并分支,最后master就会指向ed489。
当Jerry再次提交代码,Git就会检查远程仓库与Jerry的本地仓库,进行对比后,发现远程仓库存在Jerry的本地仓库不存在的代码,就需要Jerry将远程仓库执行git pull后,自行解决冲突。
上面说了分支基本原理,已经管理分支出现的问题,下面我们就来一步一步的深入操作分支的基本命令。
新建分支
Git新建一个分支的命令为:git branch <分支名字>,新建立后分之后,切换分支的命令为:git checkout <分支名字>。
新建分支的实质:「就是新建立一个引用,指向当前提交,master就好比一个引用」;切换分支的实质:就是将HEAD由指向原来的引用,重新指向要切换的分支的引用上:
当然上面创建分支并且合并分支的两条命令可以合并成一条命令:git checkout -b <分支名字>
当切换分之后,每次commit提交代码时HEAD指针就会跟随着新的bran分支移动,形成bran分支上的每一个版本:
假如,在新的bran分支上开发到某一个版本,再次切换回master分支进行开发就会形成分叉:
查看分支
当分支创建好了,你可以通过:git branch,来查看自己本地的分支情况:
分支前面带有*号的表示当前的分支,查看分之后,你就可以很清楚的知道自己要checkout哪条分支了。
合并分支
开发玩自己模块后,后面就会在自己本地进行合并分支,合并分支的命令:git merge <分支名字>,它表示「合并指定的分支到当前分支」,比如:当前分支为master,执行:git merge bran,表示合并bran分支到当前master分支上。
分支合并也会有失败的情况,当你的两条分支都修改的相同的文件,这时候Git就无法判断你要保留哪一个修改,就会出现merge冲突。
例如:我先在master分支修改README.md文件,然后提交本地仓库:
然后切换回分支dev,再次修改README.md文件,再次提交
最后进行合并分支,此时在你两次修改的README.md文件中就会出现两次修改的冲突代码:
因为你两次修改同一文件的操作,合并后Git并不知道你要保留哪一次的操作,所以它就会将这个决定交给你自己决定,它只告诉你文件中哪里的代码冲突了,具体怎么改就由你自己去弄。
删除分支
最后是删除自己新建的分支,通过:git branch -d <分支名字>,进行删除分支,假如分支删除不了,可以通过:git branch -D <分支名字>,强制删除分支:
Git中删除分支的实质:dev只是一个分支的引用,所以删除分支也就是删除这个引用,并不会删除任何conmit,所以删除操作也是非常高效的。
假如一条分支commit的引用被删除,那么这条分支的就没有任何引用指向,这样就会找不到这条分支,最后就会被Git回收机制回收。
查看远程
在多人协作的团队下,你可能要随时查看远程仓库的情况,可以通过:git remote,进行查看,加上-v参数可以查看远程仓库的详细情况。
推送分支
分支的推送到远程上一节已经提过,使用git push命令就可以进行分支的推送,命令后面加上分支的命令,表示具体推送哪条分支:
拉取分支
分支的拉取使用git pull命令,这条命令相当于以下两条命令:
但是一般实际工作中,都可能会直接使用git pull命令:
分支管理策略
在合并分支的时候,Git会以快速合并的模式进行合并(Fast forward),但是这种模式删除分支后,会丢失分支的信息。
Git中还可以以「普通模式」进行合并,在原来git merge命令后面加上–no-ff参数即可,合并的命令如下:
临时存取工作区的改动
在开发中,若是某一时刻你想把当前的改动临时进行存放起来,可以使用git stash命令,它表示将改动的文件存储到一个独立的存储区域,并不会被提交,当再次需要的时候可以随时取出来。
这里要注意的是:「git stash的是改动的文件,也就是被Git追踪的文件,新添加的文件并没有被Git追踪,所以git stash并不会stash」。
git stash命令也可以加上save命令后面再加上备注信息,方便查看:
git stash成功后「本地的工作目录的代码会和本地仓库一样」,git stash后可以通过git stash list命令查看之前stash的历史记录,当再次需要将改动的文件取出来时候,可以通过以下命令:
git stash pop表示「弹出第一个被stash的记录,并且该stash会从历史记录中删除」;也可以使用git stash apply命令「弹出stash,但是这条命令stash任然会保存在stash历史记录中」,你也可以通过:git stash drop命令来删除。
这一篇就只讲解了Git的分支原理以及Git的临时存取操作,限于篇幅,我们今天就到这里,我们下一期继续图解Git操作,我们下一期再见。
这一篇我们继续图解Git,上两篇原创图解了Git的基本操作,有兴趣的可以看一看[]和[]。
这一篇写完基本Git的操作就图解完了,如果想深入了解Git,这里可以推荐一些Git的硬核书籍:【精通Git】、【GitHub入门与实践】、【Git权威指南】、【Git版本控制管理】、【GitHub实践】。
这些都是一些关于Git的比较好的书籍,有兴趣的可以可看一看,网上也有很多电子书。闲话不多说,下面就开始我们的正题。
日志
Git查看日志前面有提到过可以通过git log命令进行查看:
git log可以查看你「提交的时间、提交的作者、以及提交的id」都可以查到,如果你觉得查询的信息太多,可以加上参数–pretty=oneline,只会显示版本号和提交时的备注信息。
如果你想查最近的几条历史记录,可以通过加参数”-n”的形式制定查询几条记录,历史记录是「按照操作的时间」进行排序的:
还可以通过加参数” –graph”,以图形化的形式展示历史记录,方便与查看历史记录与分支的关系:
还可以加参数”-p”,可以查看每一个commit操作更改的文件的哪一行,加参数”-stat”查看哪些文件改动了,进行简要的统计。更加详细的git log参数可以查看命令帮助。
Git查看历史记录的另一个命令是git reflog,它可以查看「所有分支的所有操作记录,包括已经删除的commit记录和reset记录」。
衍合(rebase)
分支管理中有「合并」和「衍合」操作,合并操作在在第二年篇的分支章节已经详细讲解过了,就来讲解一下衍合操作:git rebase操作。
假如有两位开发人员Tom和Jerry,Tom和Jerry都把远程的master分支签出到本地,此时当前的Tom和Jerry本地都是只有一条master分支:
此时Tom开发人员,创建一条新的分支branch,并且将新的分支branch推向远程仓库(git push origin branch):
此时Tom本地仓库和远程仓库的分支保持一致,分支如下图所示:
Tom在自己的分支branch上开发自己的模块,假如开发期间Tom进行了「两次」的提交,最后Tom本地的分支形成如下所示:
也可以通过git log查看两次提交的记录:
若是,此时Tom开发人员准备把自己的branch的分支推向远程仓库,但是,Jerry在此之前已经在master提交了自己的开发代码,所以master分支相比之前记录,已经向前推进了一个版本。
所以此时Tom想提交,必须先更新一下自己的本地分支:
Tom中通过git log命令可以查看到Jerry的提交记录情况,说明此时分支已经与远程仓库同步:
此时Tom更新分之后,本地的分支情况如下所致,相比原来master指向c1,现在向前推进了一个版本指向c4:
此时,Tom必须重新合并分支进行提交,把branch的代码合并到master分支上,现在Tom可以有两种方式:
直接git merge。先git rebase,然后git merge。(1)若是使用第一种方法直接在master分支上执行git merge命令,「Git会把master分支上最新的提交c4的内容和branch分支上最新的提交c3 合并后生成一个新的提交点c5」:
上面实在没有冲突的前提下,若是有冲突,则解决冲突:
此时就完成了master和branch分支的合并。
(2)若是使用第二个方法,先master也需要拉取到最新版本,然后是切换到branch分支,这个也就是要切换到rebase的分支,这里指的是branch分支。
在branch分支执行git rebase master,表示chanch上新提交的c4节点会在master上的最新提交点后重新设立起点重新执行,若是有冲突则解决冲突,没有冲突执行git add,最后执行git rebase –continue。
通过git log查看你可以发现,之前branch分支上进行【「删除HELLO ABC」】的操作commit hash已经发生改变,最后切换到master分支,执行git merge master。
通过测试可以发现,原来branch分支上重新commit的节点c4在rebase到master分之后hash发生了改变,并且提交的内容被复制保留,从而使得master分支整个呈现线性的commit记录,而不是直接git merge后的分叉记录。
在master分支进行branch分支rebase的原理图如下:
注意:「一般不是在branch进行rebase主分支master的提交,因为会导致master的新提交在本地丢失,这样有可能会导致本地仓库与远程仓库发生冲突,从而无法push操作」。
「总结」:「git merge会将两个分支的最新提交点进行一次合并,形成一个新的提交点,最终形成树状的提交记录」,但是有些人并不是喜欢merge,觉得merge之后出现的分叉会难以管理,那么可以选择rebase操作来替代merge。
「git rebase操作是将要rebase的分支最新提交点作为新的基础点,将当前执行git rebase master的分支的新commit点重新生成commit hash值,rebase完后再次切换到另一条分支进行合并,就可以保证线性的commit的记录」。
「最后只选择merge还是rebase取决于个人和时机情况,假如你想提交记录呈现线性整洁那么选择rebase,否则选择merge,实际情况也有可能是这样的,每个人本地开发,可能会提交非常的多次,有些提交可能是修一些简单的bug,那么最后的提交只想做一次完整、正确的提交,那么也可以使用rebase。」
标签管理
Git中使用的标签有两种类型:「轻量级的(lightweight)和含附注的(annotated)」。轻量级标签只需在git tag后加上标签的名字,就可以添加标签。
标签管理作为开发人员可能很少使用,可以作为了解,「tag是针对Git中某一时间某一版本打上标签」,tag的使用命令也是非常的简单。
新建标签
新建一个标签,默认是在HEAD新建,可以指定commit id新建,具体命令如下所示:
删除标签
删除标签若是标签没有推向远程仓库,直接使用以下命令删除:
若是标签已经推向远程仓库,先删除本地,再删除远程仓库的标签:
查看标签
git tag的方式是查看所有的标签,git show <标签名>的方式是查看每个特定的标签
推送标签到远程
推送标签也是分两种情况,一种是指定标签的推送,另一种是推送所有标签。
提交了错误代码
代码错误提交了怎么办,重新再一次提交一个版本呗,这个可能是很多人的解决方案,当然Git也是有提供自己的解决方案的命令。
第一种就是再次将修改文件然后git add .到暂存区,最后git commit –amend提交修改 ,它的原理图如下所示:
这种方法只能修改当前HEAD,也就是最新的提交,那么要修改是倒数第二个或者倒数第三个的提交呢?
这时候就要使用rebase -i(「交互式rebase」)进行操作了,这个命令是指定commit链中哪一个commit需要修改。
比如执行命令:git rebase -i HEAD^。它表示的含义就是把当前commit内容rebase到HEAD之前的一个commit上。
若是想直接丢弃最新的commit的修改,则直接使用命令:git reset –hard HEAD^。他表示当前commit往前移动一次。
那想丢弃某一次的修改呢?并不是最新的commit,通用也是要使用rebase的交互式操作:git rebase -onto HEAD^^ HEAD^ master。他表示的含义就是以倒数第二个为起点,master为终点,rebase到倒数第三个commit上。
最后福利
好了图解Git操作基本讲解完了,其他的一些细节操作基本都是在基本操作的基础上加参数,详细的参数大家可以参考官网或者相关的书籍。
在公司的实际应用这三篇图解Git操作基本可以应付了,上面说的交互式操作,基本没用过,只做大致的了解,但是之前在面试华为的时候有被问到Git的交互操作。
2:00。