1.简介
1.1 关于版本控制
版本控制是一种记录一个或若干文件内容变化,以便将来查阅特定版本修订情况的系统。
1.2 版本控制发展史
1.2.1 本地版本控制
记录文件每次的更新,可以对每个版本做一个快照,或是记录补丁文件,适合个人用,如RCS。
1.2.2 集中式的版本控制
所有的版本数据都保存在服务器上,协同开发者从服务器上同步更新或上传自己的修改
所有的版本数据都存在服务器上,用户的本地只有自己以前所同步的版本,如果不连网的话,用户就看不到历史版本,也无法切换版本验证问题,或在不同分支工作。而且,所有数据都保存在单一的服务器上,有很大的风险这个服务器会损坏,这样就会丢失所有的数据,当然可以定期备份。代表产品:SVN、CVS、VSS
1.2.3 分布式版本控制系统
所有版本信息仓库全部同步到本地的每个用户,这样就可以在本地查看所有版本历史,可以离线在本地提交,只需在连网时push到相应的服务器或其他用户那里。由于每个用户那里保存的都是所有的版本数据,只要有一个用户的设备没有问题就可以恢复所有的数据,但这增加了本地存储空间的占用。
1.3 什么是git
Git是目前世界上最先进的分布式版本控制系统。
优点:
- 适合分布式开发,强调个体。
- 公共服务器压力和数据量都不会太大。
- 速度快、灵活。
- 任意两个开发者之间可以很容易的解决冲突。
- 离线工作。
缺点:
- 模式上比SVN更加复杂。
- 不符合常规思维。
- 代码保密性差,一旦开发者把整个库克隆下来就可以完全公开所有代码和版本信息。
2.本地操作
2.1 安装git
2.1.1 window安装
在官网下载安装文件,双击打开,然后一直“NEXT”就好。 下载地址如下: git-scm.com/download/wi…
2.1.2 linux 安装
如果你想在 Linux 上用二进制安装程序来安装 Git,可以使用发行版包含的基础软件包管理工具来安装。 如果以 Fedora 上为例,你可以使用 yum:
sudo yum install git
如果你在基于 Debian 的发行版上,请尝试用 apt-get:
sudo apt-get install git
2.2 初始化一个本地仓库
我们现在要初始化一个本地仓库:
git init
创建之后,目录下会多一个".git"的文件夹,这个项目git的配置都在这个文件夹下面。一般我们不需要修改此文件夹下的内容。
2.3 新增修改文件并且提交到git
我们现在需要提交一个文件到git上应该如何做呢?我们先来看一个栗子:
vi readme.md // 新增一个文件
## --- start
这个一个git测试文件?
## --- end
git add . // 更新到暂存区
git commit -m "build: 初始化项目" // 更新到资源库
执行后出现了个错误:
这里是说我们得告诉git我们是谁,所以执行一下下面的命令
git config --global user.email "tjlovecl@example.com"
git config --global user.name "tjlovecl"
再次执行 commit 命令
git commit -m "build: 初始化项目"
这样文件就提交上去了。
这里简单说明一下:
Git本地有三个工作区域:工作目录(Working Directory)、暂存区(Stage/Index)、资源库(Repository或Git Directory)。
- Workspace:工作区,就是你平时存放项目代码的地方
- Index / Stage:暂存区,用于临时存放你的改动,事实上它只是一个文件,保存即将提交到文件列表信息
- Repository:仓库区(或本地仓库),就是安全存放数据的位置,这里面有你提交到所有版本的数据。其中HEAD指向最新放入仓库的版本
总结:
-
我们使用 git add 工作区的文件添加到暂存区。
-
使用 git commit -m "xxxx" 将暂存取的内容推到仓库。里面的内容信息可以参照 “Conventional Commits” 书写规则。
2.4 查看历史纪录和状态
我们可以使用 git status 查看状态, 使用 git log 查看历史纪录。
我们来看下面的栗子:
我们先建立三个文件
vi a.txt // 新增 a.txt 文件
## --- start
aaa
## --- end
git add .
git commit -m "feat: 新增a.txt文件"
vi b.txt // 新增 b.txt 文件
## --- start
bbb
## --- end
git add b.txt
vi c.txt // 新增 c.txt 文件
## --- start
ccc
## --- end
我们使用 git status 查看状态
git status
我们可以看到 a.txt 已提交到版本库里面,这里不存在, b.txt 在暂存区中, c.txt在工作区。
我们使用 git log 查看历史纪录:
2.5 版本回退
2.5.1 工作区的回退
我们先把上面栗子中的文件提交到资源库:
git add .
git commit -m "feat: 新增b,c模块"
然后对文件进行修改:
echo ddd >> c.txt // 修改c.txt文件
echo ddd > d.txt // 新增d.txt文件
git status
我们可以看到, c.txt之前存在版本库中,我们需要把修改丢弃。 d.txt在版本库中没有,所以我们需要删除文件。 使用如下命令:
git checkout -- c.txt
rm d.txt
总结:
- 对于版本库中存在的文件使用 git checkout -- <filename> 命令放弃修改
- 对于版本库中不存在的文件使用 rm <filename> 命令删除文件
2.5.2 暂存区回退
当文件已提交到暂存区,我们应该如何回退呢?我们先来修改一些文件,并且提交到暂存区
echo ddd >> c.txt // 修改c.txt文件
echo ddd > d.txt // 新增d.txt文件
git add .
git status
使用如下命令移除暂存区,让文件回到工作区,然后使用上一节的方法丢弃修改
git reset HEAD .
git status
总结:
- 使用 git reset HEAD <filename> 命令使文件移除暂存区,回到工作区
2.5.2 资源库版本切换
我们先新增修改了一些文件,并且把他们提交到了暂存区,然后再新增修改一些文件,这些文件在工作区
echo ddd > d.txt // 新增 d.txt 文件
echo aaa >> a.txt // 修改 a.txt 文件
git add . // 把d.txt 和 a.txt文件加入暂存区
echo fff > f.txt // 新增 f.txt 文件
echo bbb >> readme.md // 修改 readme.md 文件
git status
接下来我们要回退到最初的版本:
我们先查看一下历史:
git log
我们查到了版本对应的git的id,接下来使用下面命令回退
git reset --hard a8b3ad1643671b30681b33c7506bec9badee6505
git status
我们发现暂存区的文件全部丢失了,工作区的修改的文件也丢失了,只保存了工作区新增的文件。
突然我们发现自己回退错版本了,需要回退到 "新增a模块"的那个版本,我们可以使用下面命令找到所以变更的版本历史:
git reflog
git reset --hard 2a39656
git log
PS:我们经常看到 git reset --hard HEAD^ 这样的命令, 这句话表示放弃修改的内容,回退当前的版本,HEAD 表示当前版本,HEAD^ 表示上一个版本, HEAD~100表示上100个版本。
总结:
- 使用git reset --hard <版本号> 进行版本切换
- 使用git reflog 查看所有的版本变更历史
- HEAD 表示当前版本
2.6 文件忽略
在项目中,又些文件我们并不需要上传到git,只需要保留在本地,比如配置文件,第三包下载的文件,日志文件等。 我们先来新建文件和文件夹。
echo abc > ignore.txt
mkdir ignore
echo abc > ignore/a.txt
git status
这里我们需要新建".gitignore"文件:
vi .gitignore
## --- start
ignore.txt // 忽略 ignore.txt 文件
ignore/ // 忽略 ignore 文件夹
## --- end
git status
发现工作区只有 .gitignore文件,其他的文件已被忽略。
总结:
- 编辑 ".gitignore" 来忽略文件
3 分支
3.1 分支介绍
分支就是科幻电影里面的平行宇宙,当你正在电脑前努力学习Git的时候,另一个你正在另一个平行宇宙里努力学习SVN。 如果两个平行宇宙互不干扰,那对现在的你也没啥影响。不过,在某个时间点,两个平行宇宙合并了,结果,你既学会了Git又学会了SVN!
分支在实际中有什么用呢?假设你准备开发一个新功能,但是需要两周才能完成,第一周你写了50%的代码,如果立刻提交,由于代码还没写完,不完整的代码库会导致别人不能干活了。如果等代码全部写完再一次提交,又存在丢失每天进度的巨大风险。现在有了分支,就不用怕了。你创建了一个属于你自己的分支,别人看不到,还继续在原来的分支上正常工作,而你在自己的分支上干活,想提交就提交,直到开发完毕后,再一次性合并到原来的分支上,这样,既安全,又不影响别人工作。
其他版本控制系统如SVN等都有分支管理,但是用过之后你会发现,这些版本控制系统创建和切换分支比蜗牛还慢,简直让人无法忍受,结果分支功能成了摆设,大家都不去用。
但Git的分支是与众不同的,无论创建、切换和删除分支,Git在1秒钟之内就能完成!无论你的版本库是1个文件还是1万个文件。
3.2 分支的创建与合并
当我们创建新的分支,例如dev时,Git新建了一个指针叫dev,指向master相同的提交,再把HEAD指向dev,就表示当前分支在dev上:
你看,Git创建一个分支很快,因为除了增加一个dev指针,改改HEAD的指向,工作区的文件都没有任何变化!
不过,从现在开始,对工作区的修改和提交就是针对dev分支了,比如新提交一次后,dev指针往前移动一步,而master指针不变:
假如我们在dev上的工作完成了,就可以把dev合并到master上。Git怎么合并呢?最简单的方法,就是直接把master指向dev的当前提交,就完成了合并:
所以Git合并分支也很快!就改改指针,工作区内容也不变!
合并完分支后,甚至可以删除dev分支。删除dev分支就是把dev指针给删掉,删掉后,我们就剩下了一条master分支:
下面开始实战:
1)创建分支
git checkout -b dev
git checkout命令加上-b参数表示创建并切换,相当于以下两条命令:
git branch dev
git checkout dev
2) 查看分支
git branch
- 修改文件并提交
echo aaaa >> a.txt
git add .
git commit -m "feat: a模块新增一行"
- 合并dev到master
git checkout master // 切回master分支
git merge dev // 合并dev分支到master
- 删除dev分支
git branch -d dev
接下来我们来探讨切换分支对于暂存区和工作区文件的影响
1)我们先来修改一些文件
echo bbb > b.txt
git add .
echo cccc > a.txt
git status
- 接下来新建并切换到qa分支
git checkout -b qa
git status
所以我们可以得出结论,有文件存在工作区或者暂存区时,切换分支后,这些文件会原封不动的带到新的分支。
我们可以看一下之前的log,并没有发现从dev合并到master的痕迹,那么又没有办法可以在日志里面留下点痕迹?
我们可以在merge命令中增加 "--no--ff"参数, 我们已上面的qa分支为例
git add .
git commit -m "feat: 新增b文件"
git checkout master
git merge --no-ff -m "chore: qa分支合并到master上" qa
git log --graph --pretty=oneline --abbrev-commit
总结:
1)Git鼓励大量使用分支:
查看分支:git branch
创建分支:git branch <name>
切换分支:git checkout <name>或者git switch <name>
创建+切换分支:git checkout -b <name>或者git switch -c <name>
合并某分支到当前分支:git merge <name>
删除分支:git branch -d <name>
2)有文件存在工作区或者暂存区时,切换分支后,这些文件会原封不动的带到新的分支。
3)合并分支时,加上 --no-ff 参数就可以用看出历史有分支曾经做过合并。
3.3 解决冲突
人生不如意之事十之八九,合并分支往往也不是一帆风顺的。
我们先修改一下master分支的a.txt文件
echo master >> a.txt
git add .
git commit -m "feat: 新增一行master"
我们再切到qa分支上,修改a.txt文件
git checkout qa
echo qa >> a.txt
git add .
git commit -m "feat: 新增一行qa"
然后我们再master分支合并一下
git checkout master
git merge --no-ff -m "chore: qa分支合并到master上" qa
git status
我们打开 a.txt
Git用<<<<<<<,=======,>>>>>>>标记出不同分支的内容。 ======= 上面的内容为当前分支的内容,下面的为合并过来分支的内容我们做相应的修改,然后提交.git add .
git commit -m "chore: 解决qa合并到master的冲突"
小结:
-
当Git无法自动合并分支时,就必须首先解决冲突。解决冲突后,再提交,合并完成。
-
解决冲突就是把Git合并失败的文件手动编辑为我们希望的内容,再提交。
3.4 bug分支
软件开发中,bug就像家常便饭一样。有了bug就需要修复,在Git中,由于分支是如此的强大,所以,每个bug都可以通过一个新的临时分支来修复,修复后,合并分支,然后将临时分支删除。
当你接到一个修复一个代号101的bug的任务时,很自然地,你想创建一个分支bug-101来修复它,但是,等等,当前正在dev上进行的工作还没有提交:
echo tmpEdit >> a.txt
git add .
echo tmpEdit2 >> b.txt
git status
我们可以使用 stash命令把修改储藏起来
git stash
git status
我们再创建bug-101分支进行修改
git checkout -b bug-101
echo bbb >> b.txt
git add .
git commit -m "fix: 修正bug101"
切回master分支,并且合并
git checkout master
git merge --no-ff -m "chore: bug101合并到master上" bug-101
取出储藏的修改
git stash list
git stash pop
git status
我们发现b.txt文件冲突了,根据上面的内容解决冲突就可以了。
总结: 修复bug时,我们会通过创建新的bug分支进行修复,然后合并,最后删除; 当手头工作没有完成时,先把工作现场git stash一下,然后去修复bug,修复后,再git stash pop,回到工作现场。 一般我们都是切独立分支修改的,所以这个方法我们用的不是特别多。
4 远程仓库的使用
上面的章节里面,我们介绍了git的本地使用,知道了在本地各版本之间进行穿梭。 但这仅限于我们自己玩,如果要大家一起工作完成一个项目,势必需要远程仓库。
虽然git是分布式的,大家可以相互传输文件,但其实很少在两人之间的电脑上推送版本库的修改,因为可能你们俩不在一个局域网内,两台电脑互相访问不了,也可能今天你的同事病了,他的电脑压根没有开机。因此,分布式版本控制系统通常也有一台充当“中央服务器”的电脑,但这个服务器的作用仅仅是用来方便“交换”大家的修改,没有它大家也一样干活,只是交换修改不方便而已。而为了方便管理,我们一般会把托管平台选为“中央服务器”。
4.1 托管平台
托管平台可以是第三方提供的,如github,码云等,也可以是我们自己搭建的(公司使用gogs自己搭建了一个)。各种托管平台操作大同小异,下面我就用第三方提供的github来当作我们的“中央处理器”。
- github 网址:github.com/
接下来我们在github上新建一个项目:
1) 注册登陆github
2) 新建仓库
4.2 本地仓库与远程仓库关联
在仓库页面,我们有两种关联仓库的方式,我们使用SSH的方式来关联。
执行下面命令:
git remote add origin git@github.com:tjlovecl/git_yanshi.git // 关联远程仓库
git push -u origin master
执行完成后会报一个权限验证不通过的错误。
要消除这个错误,需要把我们服务器的SSH公钥放到github上。
4.2.1 生成ssh公钥
我们使用下面命令生成公钥和私钥。
ssh-keygen -t rsa
ll ~/.ssh
把公钥的内容复制到gitthub上。
cat ~/.ssh/id_rsa.pub
我们再执行一次,就可以把master 分支推到远程仓库中去了。
git push -u origin master
小结:
- 我们使用 git remote add origin <远程地址> 关联远程仓库
- 第一次使用 git push -u origin master 推送
- 使用SSH协议,需要配置SSHkey
- 以后再次推送直接使用 git push 即可
4.3 推送远程分支和删除远程分支
我们把qa推送到远程仓库:
git checkout qa
git push --set-upstream origin qa //第一次使用和远程qa分支想关联
以后可以使用下面命令来推送
git push
使用下面命令来删除远程分支:
git push origin --delete qa
4.4 从远程仓库克隆
如果我们本地没有仓库,需要把线上的仓库克隆下来,可以使用下面的命令,这里我们使用http的方式
cd ~
git clone https://github.com/tjlovecl/git_yanshi.git -b qa yanshi
我们来解释一下里面的两个参数
- -b qa: 表示克隆qa分支,如果不加,默认拷贝master分支
- yanshi: 把克隆下来的内容放在"yanshi"文件夹下面
如果我们我们本地有仓库,只要拷贝线上的分支到本地: 可以使用如下命令:
cd yanshi
git checkout -b master origin/master
小结:
- 使用 git clone github.com/tjlovecl/gi… [-b 分支名] [目录名] 来克隆仓库
- 使用 git checkout -b [本地分支名] origin/[线上分支名] 来克隆线上分支
- 我们不建议使用htts的方式,因为https除了慢,还要经常输入口令非常麻烦
思考题
如果我们远程分支推错了怎么办?
5. 操作git的两种工作流程
1)第一种
- 把远程的dev分支拷贝到本地
- 修改dev分支,并push给远程仓库
- 远程仓库把dev分支合并到qa
- 远程仓库把qa合并到prod
2)第二种
- 从远程prod分支check出一个tmp分支
- 修改tmp分支,把tmp分支合并到远程仓库的dev
- 把tmp分支合并到远程仓库的qa
- 把tmp分支合并到远程仓库的prod
思考题:
这两种方式孰优孰劣?我们应该如何选择?