git学习笔记
1、体系结构
之前学习了git,今有恰逢工作需要,所以以一种边复习边总结的方式写下这篇博文,希望大家能够喜欢,有不足之处望大家指出,一起进步。
首先放一张体系结构导图,本文也是基于这张导图的顺序进行叙述的。
2、前期准备
2.1、git简介
2.1.1、什么是git
与svn等工具一样,git也是一个版本控制工具。与svn不同的是,git是分布式的、且多人协作开发更加方便,且只需要在推送远程时才需要连接互联网,避免了像svn一样连接互联网开发时网速过慢的问题。
2.1.2、为什么需要git
可能有人会问了,为什么需要git呢,没有git不是一样开发。是的,没有git这样的版本控制工具我们的确可以开发,但是我们开发的不舒服,或者说是效率不高,因为我们将一些精力放在了繁杂且没有必要的代码管理与版本控制工作上了,明明这些事情可以通过工具解决我们为什么不用呢,毕竟工欲善其事必先利其器。这里引用廖雪峰老师git官网上的一个例子来当作这篇博文的敲门石吧。
如果你用Microsoft Word写过长篇大论,那你一定有这样的经历:
想删除一个段落,又怕将来想恢复找不回来怎么办?有办法,先把当前文件“另存为……”一个新的Word文件,再接着改,改到一定程度,再“另存为……”一个新文件,这样一直改下去,最后你的Word文档变成了这样:
过了一周,你想找回被删除的文字,但是已经记不清删除前保存在哪个文件里了,只好一个一个文件去找,真麻烦。
看着一堆乱七八糟的文件,想保留最新的一个,然后把其他的删掉,又怕哪天会用上,还不敢删,真郁闷。
更要命的是,有些部分需要你的财务同事帮助填写,于是你把文件Copy到U盘里给她(也可能通过Email发送一份给她),然后,你继续修改Word文件。一天后,同事再把Word文件传给你,此时,你必须想想,发给她之后到你收到她的文件期间,你作了哪些改动,得把你的改动和她的部分合并,真困难。
于是你想,如果有一个软件,不但能自动帮我记录每次文件的改动,还可以让同事协作编辑,这样就不用自己管理一堆类似的文件了,也不需要把文件传来传去。如果想查看某次改动,只需要在软件里瞄一眼就可以,岂不是很方便?
这个软件用起来就应该像这个样子,能记录每次文件的改动,git就是这样的一个软件。
2.2、git安装
访问git官网,进去会看到下面的界面,点击downloads
进入下载页面,根据电脑系统下载相应的安装包
点击安装文件,一路next即可完成安装,安装完成后在桌面单击鼠标右键会多出来两个选项,他们分别是git图形化工具(Git GUI Here)和git命令行工具(Git Bash Here)。
2.3、git用户配置
第一次使用安装完git我们要进行用户配置,因为git是分布式的,那么大家同时向同一个远程仓库进行代码推送时应该告诉别人在某时某刻是谁做了什么操作对吧,因此我们要为自己的git配置一个用户名和邮箱方便别人查看。具体配置方法如下:在桌面空白处单击鼠标右键,点击Git Bash Here进入git命令行工具,你会看到一个类似与windows的DOS命令窗一样的东西,这个就是git命令行,下面我们开始配置用户标识:
git config --global user.name "用户名随意"
git config --global user.email "邮箱随意"
以上的全局配置是为整个计算机配置,即每一个git项目进行提交、推送等操作时的操作者都是该用户和邮箱,当然了也可以为每一个项目做项目级别的用户配置。若在用户目录下生成了.gitconfig文件则证明配置生效了。
3、git基础(本地版本库操作)
3.1、创建本地版本库
在任意位置创建一个空文件夹,此时的这个文件夹就是一个普通的文件夹,我们对其任何操作都不会被git感知,若想让其变成能够被git管理的文件夹(本地版本库),我们需要将其初始化为一个git仓库,注意这里的git仓库和本地版本库是一个称呼。初始化一个git仓库使用下面的命令:
git init // 初始化git仓库
注意该命令必须要进入目标目录下(下图中的test目录)执行才有效,执行成功结果如下:
且在目标目录下会创建一个.git目录,这个目录我们会在后面讲到,不要尝试着去删除它。
3.2、三区介绍
在git中存在着三个区,他们分别是工作区、暂存区、对象区(版本库),说白了我们版本库中的每一个文件都是在这三个区之间进行流转的。
- git工作区
就是你的版本库目录包括目录里面的所有文件和文件夹(不包括.git目录),比如我的版本库是test目录,那么此时的工作区就是整个test目录(不包括.git目录),直白点说就是我们平时存放项目代码的地方。即下面的图:
- 对象区(版本库)
.git目录就是对象区,该目录内包含了我们本地版本库的各种信息,千万不能删除该目录,否则我们的版本库也就不存在了。 - 暂存区
用于临时存放你的改动,事实上它只是一个文件,保存即将提交到文件列表信息 - git工作流程
1、在工作目录中添加、修改文件;
2、将需要进行版本管理的文件放入暂存区域;
3、将暂存区域的文件提交到git仓库。 - git中文件的四种状态
1、Untracked:未跟踪,此文件在文件夹中,但并没有加入到git库,不参与版本控制。通过git add 状态变为Staged;
2、Unmodify:文件已经入库,未修改,即版本库中的文件快照内容与文件夹中完全一致。这种类型的文件有两种去处,如果它被修改,而变为Modified。如果使用git rm移出版本库,则成为Untracked文件;
3、Modified:文件已修改,仅仅是修改,并没有进行其他的操作,这个文件也有两个去处,通过git add可进入暂存staged状态,使用git checkout 则丢弃修改过, 返回到unmodify状态,这个git checkout即从库中取出文件,覆盖当前修改;
4、Staged:暂存状态,执行git commit则将修改同步到库中,这时库中的文件和本地文件又变为一致,文件为Unmodify状态,执行git reset HEAD filename取消暂存,文件状态为Modified
3.3、添加文件并提交
现在我们试着在test目录下新建一个a.txt文件,并编辑文件内容为aaa,然后使用git status命令查看当前版本库状态,git status命令是我们最常用的命令之一,它能够告诉我们我们当前在哪个区,以及我们能够执行什么命令;
git status命令告诉我们a.txt文件的文件状态是Untracked,并且提示我们使用git add命令可以让git来跟踪a.txt文件,将其纳入git的管理;我们根据提示执行git add命令,然后在使用git status命令查看工作区状态;
现在发现文件状态与之前不同了,现在提示多了一个新文件a.txt,并且提示我们可以使用git rm --cached将其状态改变为Untracked;现在我们使用git commit -m '备注’命令将暂存区的内容提交至本地版本库,并查看状态;
每次提交后就会生成当前版本库的一份快照,并且用当前提交点的sha1值代表引用当前版本库,方便我们以后进行版本回退操作。
总结: 我们在开发中最常用的三个命令就是git add 、git commit、git status
3.4、git 删除文件
这里删除分别操作系统删除和git命令删除两种,我们逐一进行讲解;
- 操作系统删除
我们尝试使用rm命令删除a.txt,而后查看其状态;
操作系统删除后的文件发现在工作区(文件提示为红色),git提示我们如果想撤销删除指令可以使用git restore命令;若想真正删除文件则先git add,然后执行git commit命令来真正删除它。
- git命令删除
我们还可以使用git rm来删除文件,然后查看文件状态
与操作系统删除不同的是,使用git命令行删除后的文件位于暂存区,若想真正删除只需要commit一把将删除指令提交以下就可以;当然了,若是我们后悔删除操作了,还可以使用git restore --staged撤销删除操作,只不过执行完git restore --staged命令以后文件就回到了工作区,与操作系统删除的结果是一样的,我们还需要额外执行git restore命令撤销删除指令;
3.5、分支管理
初始化一个git仓库后,git默认会为我们创建一个master分支,但是在实际开发中我们并不建议在master分支上进行开发,而是在其他分支上进行功能开发,当功能开发完毕后再合并到master分支上。需要注意的是所有的分支都是并行的关系而不是树型(父子)结构。
我们可以使用git branch -av来查看所有的分支,比如我现在查看test仓库中的所有分支。创建分支我们可以使用以下的命令:
git branch '分支名' // 创建分支
git checkout '分支名' // 切换分支
git checkout -b '分支名' // 一步到位,创建并切换分支
git branch -d '分支名' // 删除分支
现在就让我们试着在本地创建一个dev分支吧
3.6、冲突处理(重点)
冲突是我们在git管理中遇到最频繁的问题,因此必须要掌握如何解决冲突。
首先讲一下什么时候会出现冲突,当在不同的分支对同一个提交点的同一个文件做了修改,那么当我们尝试着合并这两个分支时就会产生冲突,什么叫做同一个提交点,下面通过两个实际案例进行介绍;
- 情况一:不产生冲突的合并
首先我们在master分支创建a.txt文件并编辑内容aaa,然后add并commit,记录sha1值
然后我们创建并切换到dev分支,对a.txt文件追加内容bbb,add并commit,记录sha1值
接着在dev分支继续对a.txt文件编辑,添加内容aabb,add并commit,记录sha1值
此时切换到master分支并合并dev分支看看结果是什么,我们可以使用git merge ‘想要合并的分支名’ 命令进行分支之间的合并,若master想合并dev分支就是切换到master分支执行git merge dev就可以完成分支的合并;结果如下:
我们根据提示知道了a.txt文件内容在原来的aaa基础上增加了两行,并且master分支上的提交点也变为了6b66130。我们发现在提示中有一个Fast-forward,这个叫做快进模式。下面由一张图解释以下什么叫做快进模式。
分析:
1、第一次我们在master分支上对文件a.txt增加了aaa内容并add、commit了,此时在master分支上版本库的最新提交点是5e70fa
2、而后我们在master分支上创建并切换到了dev分支上,此时dev分支上的版本库的最新提交点也是5e70fa,因为它是在master分支上创建的,它初始就拥有和master一样的版本库。
3、然后我们在dev分支上进行了两次提交,至此dev分支的提交点如上图所示
4、切换到master分支上,此时master分支上的最新提交点是5e70fa,现在master合并dev分支不会发生冲突,因为dev分支要比master提前两个提交点,所以master分支的head指针直接指向了dev分支的最新提交点6b6613
结论:
1、若一个分支合并一个提交点比它提前的分支git默认使用Fast-forward模式进行合并
2、若一个分支合并一个提交点比它提前的分支不会产生冲突 - 产生冲突的合并
首先查看一下master分支和dev分支的提交点
1、因为之前master分支合并过dev分支,所以两个分支此时的提交点是一样的;现在我们先在master分支上编辑a.txt添加内容master conflic,然后add,commit
2、现在切换到dev分支上也对a.txt文件增加一行内容dev conflic,然后add,commit
3、我们切换到master分支试着合并一下dev分支看看会发生什么?
git提示我们自动合并失败了,因为有一个文件内容冲突了,这点容易明白,因为master和dev分支在同一个提交点对a.txt文件做了修改,master分支增加了一行master conflic,dev分支增加了一行dev conflic,现在要合并两个分支的内容,git不知道a.txt文件的最后一行内容究竟为master conflic还是dev conflic,所以会产生冲突。解决冲突也很简单,我们只需要对冲突文件进行编辑,手动解决冲突即可,我们现在对a.txt文件编辑一下看看会发生什么。
进入vim界面我们会看到上图内容,我们只需要将上图内容编辑为我们最终敲定的内容即可,这样就解决完了冲突。但是这样还不够,因为只是解决了冲突,还没有让git帮助我们合并分支呢,我们还需要执行git add、git commit 告诉git我们已经解决了冲突,现在帮助我们合并分支吧。
查看一下master和dev分支提交日志:
我们在上面的图的基础上对冲突情况做一个总结:
1、一开始master和dev分支提交点都是在6b6613,此时dev分支先对a.txt文件进行了编辑,而后切换到master分支上也对a.txt文件进行了编辑,因为二者都是在提交点6b6613对同一个文件a.txt进行了修改,所以在合并时必然发生冲突;
2、我们对比无冲突的情况合并还可以发现,有冲突的合并,合并后两个分支没有归于同一个提交点,比如上图的master分支的提交点领先了dev一个,原因就在于我们在master分支解决完冲突后的那一次commit;
3.7、版本回退
在实际开发中,我们大多时候都是在本地库进行开发,只有当开发的差不多的时候可能才会向远程推送一下代码,并且我们都是先提交到本地库而后才向远程进行推送,那么就涉及到了本地库版本回退问题。有时候我们可能发现当前代码有问题需要回退到上一版本重新进行开发,那么我们就需要回退至上一版本,当然了若你克隆下来项目以后没有过提交而是一直在开发,那么你也可以直接重新克隆一个项目就可以实现同样的效果,我之前就是这样干的,但是你一旦有过提交了以后这样重新克隆的做法就回到不你上次的提交点了,这时我们就急需版本回退。下面让我们一起实操一遍吧。
1、先创建一个b.txt文件,编辑内容bbb,add、commit,记下提交点sha1值
2、接着继续编辑b.txt文件,增加一行111,add、commit,记下sha1值
3、继续编辑b.txt增加一行内容222,add、commit,记下sha1值
此时b.txt文件的内容是上图所示,现在我们继续开发我们的功能,但是有一天发现我们现在开发的代码与业务割裂了,现在若是一个个的删除、修改,若是开发周期短的话还好,修改的地方不多,若是开发了很久了想让代码回到一开始的状态肯定是很困难的,这时我们可以使用版本回退功能,命令如下:
git reset --hard '想回退版本的提交点sha1值(前5位一般就可以)'
比如我们现在想回到上一个提交点,即b.txt文件内容为bbb 111的版本,我们只需要找到这个提交点的sha1值前5位就可以了,若是不记得提交点的sha1值,可以使用如下命令列出历史提交点sha1值
git reflog // 列出历史提交点sha1值
根据列表和提交注释我们知道目标提交点(即b.txt内容为bbb 111)的sha1值为d56abf8,现在我们可以使用命令进行版本回退了
查看b.txt文件内容发现我们已经回退到了上一个版本。只要知道目标版本提交点sha1值我们可以回退到任意版本。
4、git高级篇(多人协作开发)
上文介绍的内容都是在本地计算机进行的单人开发,但是在实际开发中我们都是多人协作开发,这时候就要求我们懂得git远程仓库的操作了。
4.1、远程仓库
我们在平时的开发中大致是以下的过程:
张三在本地计算机开发完version 1版本,此时张三将version 1推送至远程仓库,李四从远程拉取version 1版本,并更新迭代为version 2将其推送至远程仓库;同理,王五从远程拉取来version 2继续开发为version 3并推送至远程仓库,此时远程仓库最新的版本为version 3,就这样一直迭代下去。
常见的远程托管网站有github、gitee、gitlab等。使用任何一个都可以帮助我们协作开发。
4.2、配置ssh免密钥登录
我们如果使用https协议建立本地库与远程库关系的话,每次推送都需要输入托管网站的用户名和密码;我们可以使用ssh协议建立本地库与远程库关联,并且使用ssh免密钥登录。我们在git命令行工具输入以下代码就可以生成ssh密钥对:
ssh-keygen -t rsa -C "配置的用户邮箱"
输入上面命令后一路回车就可以了,此时进入用户目录下会有一个隐藏目录.ssh,该目录内有两个文件id_rsa和id_rsa.pub,这两个文件就是密钥对,以pub结尾的是公钥需要设置给托管网站,另外一个是私钥,存在于本地计算机。
打开托管网站(需提前注册),这里以gitee为例,找到设置SSH公钥,会出现下面界面:
title填写任意值,公钥填写id_rsa.pub文件内容,点击添加就配置好了SSH免密钥对,以后使用ssh协议建立本地与远程仓库推送时就不需要输入用户名和密码了。
4.3、本地与远程仓库pull、push
pull:从远程仓库拉取代码并合并到本地当前工作分支;
fetch:从远程仓库拉取代码到本地
push:推送本地代码到远程仓库
pull=fetch+merge
4.3.1、本地库推送至远程仓库(先有本地库,后有远程仓库)
我们在本地创建一个版本库test并添加a.txt文件add、commit,在远程创建一个仓库test,将本地版本库推送至远程仓库;这里分为两种情况,第一种情况是创建的远程仓库是空的,第二种情况是远程仓库是由内容的,下面分别进行试验:
- 情况一:远程仓库为空
1、首先创建远程仓库,以gitee为例
创建完成后会自动跳转到仓库首页,
查看本地test仓库
此时的本地版本库还没有和远程版本库进行关联,我们使用以下命令关联本地版本库与远程版本库
git remote add origin '远程仓库地址地址(ssh和https协议均可)'
git remote rm origin // 若想更换与本地库绑定的远程库执行该命令,然后重新执行添加远程仓库的命令即可
执行完该命令以后远程仓库的名字就叫做origin,以后origin就代表远程仓库,我们可以使用git remote -v命令查看与本地版本库关联的远程库;
现在我们将本地版本库内容推送到远程库,使用下面命令:
git push -u origin master
在第一次推送时加上-u参数将本地master分支与远程master分支绑定起来了,实际上是将本地master分支推送远程master,以后master分支推送远程时就不需要后面的一大串了,只需要执行git push即可完成推送。
2、在推送之前我们先确定以下远程仓库的情况
可以发现远程仓库是空的,现在我们执行git push命令进行推送
刷新以下远程仓库,发现本地版本库内容已经更新至远程了
3、本地修改a.txt文件增加一行内容bbb并推送至远程仓库
刷新远程仓库查看a.txt文件内容如下
- 情况2:远程仓库不为空
1、创建一个远程仓库,在创建时我们勾选readme.md文件,这样远程仓库就不为空了。
创建完成后跳到仓库首页,可以看到与之前创建的空仓库显示页面不一样。
2、在本地创建本地库test2
3、和之前一样,我们先将本地仓库与远程仓库进行关联
与之前不同的是,若创建的远程仓库不为空,我们在关联完仓库以后不能直接将本地库推送至远程仓库,原因很简单,此时若直接推送会冲突,因为远程库不是空的(含有readme.md文件),我们可以试试直接push看看会发生什么?
我们先得把远程库的内容pull到本地库再进行推送,所以我们先拉取远程仓库的内容到本地,使用下面命令将使用将远程的项目与本地合并
git pull —-rebase origin master // 远程的项目与本地合并
若出现以上界面则合并成功了,现在可以push了,将本地推送至远程
提示: 第一次执行该命令可能会报以下错误
fatal: 'origin' does not appear to be a git repository
fatal: Could not read from remote repository.
Please make sure you have the correct access rights and the repository exists
多执行几遍就可以成功了,至于原因本人还需请教前辈,若有知道的大佬也可在评论区指出供大家一起学习。
和远程仓库为空第一次推送一样我们需要加上-u参数,后续本地master推送远程master时直接使用git push即可,刷新远程仓库查看内容是否更新
可以发现本地库内容已经更新至远程库。且本地库也已经有了远程库创建时的readme.md文件,两者保持一致
4.3.2、克隆
前面讲的都是将本地库推送至远程,即先有本地库后有远程库;若是先有远程库,没有本地库该怎么办呢?这时候我们可以使用git clone '远程库地址(ssh/https均可)'将远程库克隆到本地,并且克隆到本地的文件就作为本地库存在。让我们试一下吧,先在远程创建一个仓库test3,为不为空都可以。
再本地随便创建一个test3目录(目录名随意)并切入该目录,执行git clone命令
执行完克隆命令后会在test3目录下创建一个与远程仓库同名的目录,该目录就是本地库,进入该目录就可以看到与远程库一样的内容。
4.3、本地与远程分支管理(重点)
上面的推送与拉取都是在本地master与远程master分支之间进行,但是在实际开发中,我们都是在多个分支上进行开发,最后才会合并到远程的master分支上进行项目的部署实施。一般推荐在本地dev分支进行开发,开发一点向远程dev分支推送一点,等都本地dev分支全部推送至远程dev分支后,再将远端dev合并到远端master分支上进行项目的部署实施。下面就讲以下非master分支之间的推送与拉取操作以及一些常见的错误。
1、首先在远端创建一个仓库test4并且创建一个dev分支
2、然后本地克隆远程库到本地,并且在本地库新建一个文件a.txt并编辑内容aaa,提交至本地版本库,最后推送至远端
注意: 克隆的项目是不需要执行git remote add origin '仓库地址’命令的,因为我们克隆的过程就已经确定了本地库与远程库之间的关联关系,我们只需要在第一次推送时加上-u参数就可以了,这里和之前讲的是一样的。
3、在远程仓库创建dev分支
4、在本地库创建并切换至dev分支,编辑a.txt文件,新增一行内容append aaa,并且提交
5、先查看一下所有的分支
发现没有远程dev分支,很简单,因为我们本地还没有关联远程dev分支。现在我们仿照之前本地master分支推送至远端master分支的做法将本地dev分支内容推送至远端dev分支看看是否能够成功,
发现推送成功了,远端切换到dev分支查看a.txt文件内容是否已更新
远端分支已经更新。这就是本地分支如何推送远端分支的方法。
现在我们查看一下所有分支
已经有远端dev分支了,因为前面的push命令将本地dev与远端dev关联起来了。
实际开发中存在这样的问题,就以上面的test4仓库为例,现在test4仓库存在两个远程分支master、dev,两个本地分支master、dev(由上图可以看出来),加入现在张三在他的电脑上克隆了test4仓库,克隆完成后进入本地库查看一下所有分支如下:
现在我们在本地创建并切换到dev分支,编辑a.txt新增一行内容append bbb,并尝试着push远程dev看看会发生什么?
分析:
1、首先有一个很有意思的事情,就是我们切换到本地dev分支后查看a.txt文件的内容和远程仓库中dev分支下的a.txt文件内容不一样,远程dev分支下a.txt内容为
aaa
append aaa
其实原因也很简单,因为本地dev分支是在本地master分支上分离创建出来的,所以它的版本库和本地master版本库一致,又因为本地master版本库和远程版本库内容一致(远程master没有合并远程dev,所以远程master中a.txt文件的内容为aaa)
2、先查看一下远程dev的提交历史
画个图说一下:
结合上图,之所以上面的命令行图示中的一系列报错均是在说本地dev的这次推送与远端冲突了,并且git告诉我们可以先pull一下远端dev,再尝试push,我们不妨试验一下
确实如我们分析的一样,远程dev分支a.txt文件与本地dev分支a.txt冲突了,我们解决完冲突并尝试着push一下
推送成功了,刷新一下远端dev并查看a.txt内容
远端已经更新。本地继续修改a.txt文件。新增一行内容append ccc,并推送至远端dev
查看远端内容
远端已经同步。
在实际开发中我们克隆一个项目到本地后肯定希望本地新建的分支的状态和远程相应的分支一样。比如我们还是在本地克隆test4仓库,我们更加希望本地新建的dev分支的初始状态和远程dev同步,而不是先pull远程,然后解决冲突,最后再push,下面介绍一个一步到位的方法,可以使本地创建的分支和远程指定分支保持同步。我们在本地克隆test4分支
现在使用一下命令创建本地dev分支并关联到远程dev分支
git checkout -b dev origin/dev
命令执行完毕后在查看一下全部分支
查看a.txt内容
与远程dev下a.txt内容一致。至此,我的git学习心得就结束了,希望能够带给大家一些收获,有表述不清楚之处可以在评论区指出。