目录
背景认识
不知道你⼯作或学习时,有没有遇到这样的情况:我们在编写各种⽂档时,为了防⽌⽂档丢失,更改 失误,失误后能恢复到原来的版本,不得不复制出⼀个副本,每个版本有各⾃的内容,但最终会只有⼀份报告需要被我们使⽤。
但在此之前的⼯作都需要这些不同版本的报告,于是每次都是复制粘贴副本,产出的⽂件就越来越多,文件多不是问题,问题是:随着版本数量的不断增多,你还记得这些版本各⾃都是修改了什么吗?
为了能够更方便我们管理这些不同版本的文件,便有了版本控制器。所谓的版本控制器,就是能让你了解到⼀个文件的历史,以及它的发展过程的系统。本章讲述的就是目前最主流的版本控制器Git。
Git的基本操作
创建Git本地仓库
仓库时进行版本控制的一个文件目录。我们想要对文件进行版本控制,就必须先创建一个仓库出来。
#在当前目录下创建一个本地仓库
git init
执行完命令后,我们可以发现当前目录下多了一个.git隐藏文件,.git目录是Git来跟踪管理仓库的。

认识工作区、暂存区。版本库
工作区:是在电脑上你要写代码或文件的目录。
暂存区:一般存放在.git目录下的index文件中,有时也把它叫做索引。
版本库:又名仓库。工作区有一个隐藏目录.git,它不算工作区,而是Git的版本库。这个版本库⾥⾯的所有⽂件都可以被Git管理起来,每个⽂件的修改、删除,Git 都能跟踪,以便任何时刻都可以追踪历史,或者在将来某个时刻可以"还原"。
· 当对工作区修改后执行git add 命令时,就会把工作区中的内容更新到暂存区**。**
· 当执行完上述操作后,执行git commit,就会把暂存区中的内容更新到版本库。
当想对工作区中的某一个文件的修改更新到暂存区时,执行git add 文件名
当想对工作区中的所有修改全部更新到暂存区时,执行git add .
如果想对当前暂存区的内容更新到版本库做注释,执行git commit -m "xxx"
当你不清楚当前工作区中的内容是否有被修改时,使用git status命令查看在你上次提交之后是否有对文件进行再次修改,以及还有哪些文件的修改需要被添加和提交。
查看历史提交记录
git log
#想让每一次提交的信息以一行的形式简单输出
git log --pretty=oneline
执行完命令后,我们发现每一次记录都有一串类似3002d...aa19b,这其实是每次提交的commit id(版本号),是由SHA1计算出来的一个非常大的数字,用十六进制表示。

查看.git文件发现
.git下的index就是我们的暂存区,add后的内容都是添加到这里。
.git下的HEAD就是我们默认指向master分支的指针。
而默认的master分支,其实就是.git/refs/heads/master。通过cat命令显示里面的内容,发现是最近一次提交的commit id。
fcas
.git下的objects是Git的对象库,里面包含了创建的各种版本库对象和内容。
objects下的commit id分为两部分,前2位是文件夹名称,后38位是文件名称。
如果我们想通过commit id查看历史版本的内容,就使用git cat-file指令。
git cat-file commit id
版本回退
上面讲述了Git会将历史提交记录保存起来,那么肯定就会存在版本回退的功能。
git reset [--soft|--mixed|--hard] [HEAD]
--soft:对于工作区和暂存区的内容都不变,只是将版本库回退到某个指定版本。
--mixed:为默认选项,使用时可以不带该参数。将暂存区的内容回退为指定提交版本的内容,工作区文件保持不变。
--hard:将暂存区与工作区的内容回退到指定版本。
HEAD:可以直接写出commit id,表示指定回退的版本。
HEAD表示当前版本
HEAD^表示上一个版本
HEAD^^表示上上一个版本
......
根据上述指令指示我们已经可以实现版本回退,但是如果再回退完版本后,我们还想回退回我们回退之前的版本怎么办,此时git log已经显示不出比当前版本更新的版本的commit id了。
Git还提供了一个git reflog命令能补救一下,该命令记录了本地的每一次命令。这样就能找到历史的任意一个版本的commit id了。

撤销工作区的修改
如果我们在工作区洗了很长时间的代码,后来觉得这部分修改没用了,想恢复到上一个版本。
情况一:工作区的代码还没有add
#让指定文件回退到最近一次的add或commit时的状态
git checkout --文件名
情况二:已经add,但是还没有commit
git reset --mixed(可以不写) HEAD 文件名
情况三:已经add,并且也commit了
#使用是有条件的:还没有本地版本库推送到远程仓库
git reset --hard HEAD^
分支管理
理解分支
分支可以简单理解为分身,比如一个你正在学习,而另一个你的分身正在运动。
在版本回退里,你已经知道,每次提交Git都会把它们串成一条时间线,这条时间线就可以理解为一个分支。而上述提到的master ,其实就是默认生成的主分支。
分支的基本操作
创建分支
git branch 分支名
当我们创建新的分支dev后,我发现.git/refs/heads目录下除了master还多了一个dev。并且查看dev里面的内容发现是最新一次提交的commit id。由此我们可以知道新创建的分支指针指向的是当前分支的最新一个版本。
查看分支
#查看有哪些分支
git branch
切换分支
git checkout 分支名
#也可以将创建和切换分支使用一条指令完成
git checkout -b 分支名
执行完上述命令后,我们发现HEAD已经指向了dev(新创建的分支),说明分支切换成功。
合并分支
由上述提到的分支定义可以知道,如果在一个分支上进行了一系列修改操作,在另一个分支上并不会看到这些修改。那怎么让它看到修改操作呢?合并分支。
#合并指定分支到当前分支
git merge 指定分支
执行完上述命令后,发现显示"Fast-forward",代表快进模式,也就是直接把master指向dev的当前提交,所以合并速度非常快。
在这种模式下,删除分支后,查看分支历史时,会丢掉分支信息,看不出来是最新提交还是merge进来的正常提交。
指定不使用Fast-forward模式:
git merge --no-ff -m "xxx" 要合并的分支名
删除分支
合并完成后,dev分支就没什么用来,就要将其删除。如果当前正在处于某分支下,就不能删除当前分支。
git branch -d dev
#可以会因为该分支的内容没有合并到别的分支上二导致删除失败
#强制删除分支
git branch -D dev
合并冲突
在实际分支合并的时候,并不是想合并就能合并成功的,有时可能会遇到代码冲突的问题。
例如:master和dev分支各自都分别有了新的提交后,合并起来就会产生冲突。

报错:

打开修改并合并的文件后发现:

<<<HEAD到===表示当前分支的内容,===到>>>表示合并分支的内容,需要自己根据需求,手动去调整冲突代码。
冲突解决后,分支状态就变为:

#查看本地仓库有哪些分支
git branch
#查看远程仓库有哪些分支
git branch -r
#创建分支
git branch 分支名
#让head指向指定分支
git checkout 分支名
#创建加切换分支
git checkout -b 分支名
#将当前分支与指定分支合并
git merge 分支名
#删除指定分支
git branch -d 分支名
#强制删除指定分支
git branch -D 分支名
#git log后置选项
git log --graph(图示历史操作) --abbrev-commit(将commit id缩短)
分支策略
master主分支是稳定的,其他分支不稳定允许存在bug。开发人员在其余分支开发并测试稳定后,将其合并到主分支上。
在开发的过程中,突然主分支出现bug,此时需要切换到主分支,创建一个修复bug的分支进行bug修复,但是不想要将正在开发的内容显示在主分支中且还无法提交,可以先工作区中的内容进行保存。
#将工作区中的内容进行保存
git stash
#显示保存了哪些内容
git stash list
#将保存的内容恢复出来
git stash pop
远程操作
理解分布式版本控制系统
我们目前所说的所有内容(工作区,暂存区,版本库等等),都是在本地!也就是在你的笔记本或者 计算机上。而我们的Git其实是分布式版本控制系统!什么意思呢?
可以简单理解为,我们每个⼈的电脑上都是⼀个完整的版本库,这样你工作的时候,就不需要联网了,因为版本库就在你再看的电脑上。既然每个⼈电脑上都有⼀个完整的版本库,那多个⼈如何协作 呢?比方说你在自己电脑上改了文件A,你的同事也在他的电脑上改了文件A,这时,你们俩之间只需 把各自的修改推送给对方,就可以互相看到对方的修改了。
分布式版本控制系统的安全性要高很多,因为每个人电脑里都有完整的版本库,某⼀个人的电脑坏掉 了不要紧,随便从其他⼈那⾥复制⼀个就可以了。
在实际使用分布式版本控制系统的时候,其实很少在两人之间的电脑上推送版本库的修改,因为可能 你们俩不在⼀个局域网内,两台电脑互相访问不了。也可能今天你的同事病了,他的电脑压根没有开机。因此,分布式版本控制系统通常也有⼀台充当"中央服务器"的电脑,但这个服务器的作用仅仅是用来方便"交换"⼤家的修改,没有它大家也⼀样干活,只是交换修改不方便而已。有了这个"中央服务器"的电脑,这样就不怕本地出现什么故障了。
远程仓库
在实际开发过程中,多人协作完成一个项目的开发,如果通过相互进行推送消息来进行版本更新效率低下,在这种情况下远程仓库应运而生,github、gitee。(下面以gitee为例子进行讲解)
新建远程仓库

进去填写一些关于仓库的基本信息就可以直接创建了。
克隆远程仓库
将远程仓库克隆/下载到本地,使用git clone。

点击这个克隆显示两个方法:HTTPS和SSH
①使用HTTPS方法
cpp
git clone HTTPS链接
②使用SSH方法(对比HTTPS方法,推送到远端时不需要输入用户名和密码了)
需要在gitee配置公钥,否则会克隆失败。
首先在本地用户主目录下查看有没有.ssh文件,有的话进入查看里面是否有id_rsa(私钥)和id_rsa.pub(公钥),没有的话使用下面的命令创建。
cpp
ssh-keygen -t rsa -C gitee上配置的邮箱
创建好后将id_rsa.pub中的信息配置到gitee



cpp
git clone SSH链接
查看远程仓库基本信息
cpp
#查看远程仓库默认名字
git remote
#查看当前用户有远程仓库的哪些权限
git remote -v
向远程仓库推送
将本地仓库的某一个分支的内容推送到远程仓库的某一个分支。使远程仓库保持最新版本。
cpp
git push origin(向远程推送) 本地分支:远程分支(如果两个分支一致后面的可以省略)
拉取远程仓库
在实际开发中,由于多人协作的关系,别人可能向远程仓库推送了新的内容,此时远程仓库的版本要新于你的本地仓库,就需要将远程仓库的内容拉取下来,使本地仓库保持最新的版本。
cpp
git pull origin 远程分支:本地分支
上述推送操作,如果在双方的两个分支已经建立连接的情况下,可以简写成git push。
拉取操作则分两种情况:
1、如果是拉取远程某个分支里面的信息内容化,就需要建立连接才能git pull.
2、如果是拉取远程仓库的内容(有哪些分支),就不用建立连接也能git pull.
建立连接
上面推送和拉取时讲到,如果双方分支建立连接,就能简化操作,那么如何建立连接呢?
cpp
#创立一个dev分支,与远程的dev分支建立连接
git branch dev origin/dev
#查看本地与远程的连接情况
git branch -vv
删除缓存分支
当远程仓库的分支已经被删除,但是本地仓库拉取下来的远程的分支缓存还存在时。为了避免开发过程中,本地缓存的远程分支越来越多,需要对分支进行删除。
cpp
#显示远程分支的状态
git remote show origin
#将远程分支缓存删除
git remote prune origin
忽略特殊文件
如果不想本地仓库的某些文件跟随修改文件一起被推送到远端,就需要在仓库建立一个**.gitignore**文件,将不想被推送到远端的文件写入该文件。
cpp
*.so 表示所有以.so结尾的文件会在推送时被忽略
#如果想添加特例
!b.so 表示推送该文件时不会被忽略
#想查看.gitignore文件中的哪条规则,让指定文件被忽略
git check-ignore -v 指定文件名
给命令配置别名
cpp
#如给status起别名
git config (--global) alias.st status (alias表示起别名的意思,别名是st)
上述命令执行后,使用git status就可以简写成git st。
标签管理
理解标签
git还提供打标签的功能,对某一次重要的提交打标签,相当于起了一个别名,当回退该版本时可以快速定位。
创建标签
cpp
git tag v1.0(标签名字) commit_id(不写就是最近的一次提交)
操作标签
cpp
#查看有哪些标签
git tag
#对标签添加一些描述
git tag -a v1.0 -m "描述信息" commit_id(不写就是最新的一次提交)
#查看标签的详细信息
git show v1.0
#删除标签
git tag -d v1.0
#将本地标签推送到远程
#推送指定标签
git push origin v1.0
#推送本地的所有标签
git push origin --tags
#删除远程的一个标签
#先删除本地的标签
git tag -d v1.0
#推送到远程
git push origin :v1.0