欢迎来到我的博客: http://chenb.in
1 Git介绍
1.1 什么是git
按官方的话说:世界上最先进的分布式版本控制工具
如果不用版本控制工具我们怎么整合代码?
这里讲述一个Git出生前的一些事:
大家都知道Linux是一个开源操作系统,是一个举全世界之力创建的一个开源操作系统,大家都把自己写好的源码发给作者Linus Benedict Torvalds(林纳斯·托瓦兹),然后linus再自己一个人Ctrl C + Ctrl V,再取其精华去其糟粕,随着代码库的不断完善,工作量也越来越大,靠人工完成代码整合的难度也越来越大,这个时候版本控制工具就显得尤为重要,但是Linus不喜欢当时比较流行的开源版本控制工具CVS、SVN,因为他们需要联网才能工作,这个时候一个名叫BitMover的公司无偿提供公司旗下的分布式版本控制工具BitKeeper给Linux社区,这样问题也就算是解决了,并且也相安无事好长一段时间,后来因为社区大牛试图破解BitKeeper的协议,BitMover一生气,就把使用权回收了,然后Linus一人,扶大厦之将倾,挽狂澜于既倒,用了两周时间开发出了现如今闻名于世的Git
1.2 集中式与分布式
集中式:
代码的版本库存放在中央服务器上,工作前从中央服务器上拉取下来,工作后再把代码上传回中央服务器,版本控制需要依赖网络
分布式:
每个人的电脑都是一个完整的版本库,大家可以直接把代码上传到本地仓库,如果需要和他人同步代码直接将代码推送给他人即可,但为了更好更方便的管理代码,一般还是会创建一个远端服务器,用来汇总所有人的代码,大部分提交都是对本地仓库而言的,版本控制不依赖网络
1.3 git安装
Linux:
各发行版命令行安装方式: https://git-scm.com/download/linux
Windows:
https://www.jianshu.com/p/414ccd423efc
MacOS:
通过homebrew安装git
-
若未安装homebrew,先安装homebrew,下方为推荐教程,换成国内源
https://zhuanlan.zhihu.com/p/59805070
-
安装git
brew install git
安装完成还需以下配置,向git自报家门:
git config --global user.name "Your Name" git config --global user.email "email@example.com"
本系统使用版本为2.29.2
1.4 git作用
-
代码汇总。项目开发往往会有很多个人参与,git的一个核心功能就是整合各个人员的代码
-
代码回滚。如果出现了项目事故,比如代码不小心删了,或者代码越改问题越大,那就可以通过git回滚到之前的版本
-
分支管理。开发人员可以创建自己的分支进行项目的开发,这样怎么样都只是影响自己的项目不会对他人造成影响
上班第一件事就是从远端仓库拉取代码到你本地,下班最后一件是就是需要把今天写完的代码上传到到公司的远端仓库(一般是Github或者Gitlab)
(具体命令稍后会介绍)
1.5 git工作区域
在开始使用之前,得先说明一下git核心的四大区域:
见下图,从右至左分别是:远端仓库 本地仓库 暂存区 工作区
具体样子见下图:

- springboot-demo目录下,除.git目录外的所有内容都属于工作区
- .git文件夹下的index为暂存区,里面存放临时改动的内容,本质上是一个文件
- .git文件夹下除index文件以外,其他的内容加起来统称为本地仓库也叫本地版本库
- 远端仓库(Remote) 一般是GitHub或者Gitlab等
2 git基本命令
2.1 本地命令
2.1.1 本地仓库搭建
-
创建文件夹
mkdir testgit cd testgit
-
初始化文件夹
git init
这里我们就创建了一个空的本地仓库,里面会自动生成一个.git的隐藏目录,用于跟踪管理版本库,由git自己维护,没事别碰他,不然可能会破坏git仓库
2.1.2 本地仓库使用
首先在工作区(写代码的地方)创建一个文件 hello.md,内容如下:
我铠他超,
为希丝特莉亚献出心脏
创建后,我们可以在,命令行通过以下命令将hello.md加入到暂存区
git add hello.md
或者
git add .
前者是添加单个文件到暂存区,后者是添加所有文件到暂存区
在通过以下命令将代码从暂存区提交到本地仓库
git commit -m "新增两行没啥用的数据"
每次commit操作相当于创建一次快照, -m参数后面跟的是快照的备注内容,这项是必填项,为了回滚时更方便找到需要回滚的版本
只有先add到暂存区的文件才可以commit到本地仓库
可通过以下命令来查看文件状态,看看是否有文件还没加入到暂存区,是否暂存区还有文件还没提交到本地仓库
git status
也可以在修改文件之后,通过git diff 文件名
来查看有何变化
例如:
我将hello.md修改为:
我铠他超,
为希丝特莉亚献出心脏,
重铸马莱荣光,我辈义不容辞
之后通过以下命令查看更新了哪些内容
git diff hello.md

可以看出新加了一行内容
之后再提交一次(今后版本回滚时用的上)
git add .
git commit -m "再加一行"
这里我们在创建一个文件world.md

之后再提交一下代码
git add .
git commit -m "创建一个新文件"
此时我们在world.md里再加一行
突然你意识到,第二句话可能对健康有所威胁,于是你想到了删除这句话,在这里我们可以直接进入文件删除最后一行,非常简单,但在代码的世界里,有时候东改一行西改一行,你根本不知道你改了哪些东西,于是你可以通过git checkout -- 文件名
丢弃修改的内容,这里的 – 很重要,代表着撤销操作,如果省略了git checkout
就会变成切换分支操作(有关分支操作后续会介绍)
git checkout -- world.md
在查看文本内容
发现内容已经撤销了,在这里有两种情况:
- 文件还没添加到暂存库,那就撤销到最近一次版本库
- 文件已经添加到版本库,那就撤销到添加到暂存区后的状态
如果你写完之后,不小心git add .
把内容添加到暂存区了,还是可以通过git reset HEAD <文件名>
,可以把暂存区的内容撤销掉,可自行测试
reset不仅可以把暂存区的内容撤销掉,还可以实现版本回滚,详见下文
如果你是个狠人,你想着干脆直接就给这个文件删了,在平时直接rm -rf world.md
就结束了,但是我们是版本控制工具呀,能有那么简单吗?我们先删除文件,之后输入git status
查看一下状态
此时,工作区与版本库就不一致了,我们可以通过git rm <文件名>
来将文件从版本库中彻底删除
但你也可能突然意识到,男人就得敢作敢当,关键是身体好抗得住揍,于是可以通过git resotre <file>
来恢复文件
命令汇总
文件加入暂存区: git add 文件名
或git add .
文件提交到本地仓库: git commit -m "备注内容"
查看文件状态: git status
查看文件修改内容: git diff 文件名
撤销文件内容: git checkout -- 文件名
从版本库删除文件: git rm 文件名
从版本库恢复文件: git restore 文件名
2.2 远端命令
前面说的都是在本地上的操作,而远端仓库如果自己一个人就搭建一个Git服务器,明显是没啥必要的,好在现在市面上有一个非常棒的网站:github.com,每个人仅需注册一个账户,就可以在这个网站上搭建远程仓库。
2.2.1 远程仓库搭建
-
先查看主目录是否有.ssh目录,没有则在命令行输入以下内容创建SSH Key,用于github认证身份时使用
ssh-keygen -t rsa -C "youremail@example.com"
-
登录github -> 点击右上角下拉菜单中的Settings -> SSH and GPG keys -> 把.ssh目录下的id_rsa.pub中的内容粘贴到Key里,目的是为了让当你上传或者拉取内容时,github能对身份进行认证
-
创建新的repository
-
创建页面如下:
-
创建后页面如下:
-
-
将先创建的远程仓库与本地仓库绑定
-
进入本地文件夹
-
输入以下命令,主要要替换成你自己的(可从上面创建后页面中找到)
git remote add origin https://github.com/bean1006/testgit.git/
-
-
通过以下命令将本地commit的内容推送的远程服务器(首次推送需要使用-u指定分支,这样git会将本地master与远端master分支绑定起来)
git push -u origin master
-
此时刷新页面就会发现远端仓库多了一个hello.md的文件,说明我们上传成功了
-
当我需要在另一台电脑快速克隆这个远程仓库的内容,则可以在另一台电脑上的命令行输入(快速克隆命令命令可在远程仓库页面看到)
git clone --depth=1 https://github.com.cnpmjs.org/bean1006/testgit.git
–depth是克隆深度,–depth=1,表示只克隆最近一次commit
- 可以用
git remote
查看远程库信息 ,或git remote -v
查看详细内容
这样今后本电脑和另一台电脑都可以通过远程仓库来同步修改内容了
2.2.2 远程仓库使用
可参照此图(和上面的一样)

当我们来到公司做的第一件事就是通过以下命令拉取代码
git pull
当我们完成今天的代码任务后,就可以通过以下内容上传到服务器
git add .
git commit -m "xxx模块完成"
git push
其中pull是直接将代码从远程仓库下拉到工作区,我们也可以通过fetch将代码拉取到本地仓库,才通过checkout拉到工作区,这里涉及到分支内容了下面会介绍
命令汇总
将本地仓库与远程仓库绑定: git remote add origin 远程仓库git地址
首次推送代码: git push -u origin master
克隆远程仓库: git clone --depth=1 远程仓库git地址
拉取代码: git pull
上传代码(需先commit,上传到当前本地分支同名的远程分支上): git push
上传代码到指定分支: git push origin 分支名
3 版本管理
3.1 版本回滚
首先查看以前的版本
git log

-
HEAD为指针,指向当前版本的master分支以及对应远端的master分支(origin默认对应远端仓库),git是通过HEAD来实现版本的切换
-
从2.2 git基本命令可知,一共提交了两次,展示结果中,git是按commit时间从近到远排列的
如果我们想回退到前一个版本,我们可以输入以下内容回到上个版本,其中一个**^**,是回退一个版本
git reset --hard HEAD^
如果回退十个版本,写十个**^明显是不现实的,所以可以写成HEAD~10**
此时查看文本内容:

发现已经回头到上一个版本了再查看日志
git log

发现最新版本已经不可查看了,那该如何回到最新版本呢,查看我们上上张图,发现commit后面会跟着一串码,而这个码就是我们的版本号,我们可以通过版本号使当前HEAD指针指向我们所需要的版本,版本号可不用写全,参考如下:
git reset --hard 5cd0
这个方法也可以回滚到任一版本(只要你能记住版本号)此时再看日志

发现最新版本又出现了,再看看文本内容

一切尽在掌握
命令汇总
查看commit记录: git log
查看缩略版commit记录: git log --graph --pretty=oneline --abbrev-commit
回退到上一个版本: git reset --hard HEAD^
回退到上n个版本: git reset --hard HEAD~n
回退到某个版本: git reset --hard commit号
4 分支管理
4.1 分支介绍
如果是一个人开发也就算了,但是往往一个项目是一群人一起开发,如果某人开发一个功能预计开发两天,第一天只开发了百分之五十,然后上传代码,那别人调用代码库就有可能会出错,但是如果等全部代码完成后再上传,那每天工作的进度就不好统计了,于是分支也就应运而生了,每个人可以创建自己的分支,每天完成任务后往自己的分支里提交代码,最后再将自己分支与总分支代码进行合并
git建议在开发时尽可能的使用分支
4.2 分支的创建与合并
当git创建时,都会默认有一个分支,也就是master分支,根据版本回滚模块,我们知道分支是这样线性运作的:

HEAD指针指向当前分支,当前分支(master)指向某一个版本(这里是版本二)
随着我们不断地commit,慢慢会变成这样

当开发到版本二的时候我们可以创建一个个人的dev分支
git checkout -b dev
上面等同于
git branch dev
git checkout dev
等同于
git switch -c dev
该命令为创建一个叫dev的分支,并将HEAD指针指向该分支,具体情况如下图

通过git branch
查看一下现在有哪些分支了
一共有两个分支,*号表示当前所在分支
之后在dev分支上上完成后续开发
我们再在hello.md里添加一行,再add+commit
此时我们的版本库变成了这样(一共提交了三次)

此时我们的master分支还停留在版本二上
如果想把dev分支上的内容整合到master分支上,那么我们需要做以下两件事:
第一步: 通过git checkout master
切换到master分支,注意,因为checkout会与前面说的撤销操作混淆,所以新版的git可通过git switch 分支名
来进行分支切换
查看此刻分支与文件内容
此时内容还停留在前一个版本上
第二步: 在通过git merge 分支名
来把dev的内容整合到master上,在查看内容
现在就变成了这个情况

然后我们在卸磨杀驴,删除掉dev分支,具体命令为git branch -d <name>
在查看此时有哪些分支
4.3 合并冲突
项目不是一个人完成的,而针对同一个文件,就有可能出现两个人同时做出了修改,那么到合并的时候,git到底听谁的?谁来合并谁?
秉承着人人平等的思想,git有着自己的解决冲突的方式
首先我们先来制造点冲突
当前我们在master分支上,我们修改一下hello.md
add+commit后再切换到dev分支修改一下hello.md(我重情重义,决定创建回来了)
git add .
git commit -m '又加一行'
git switch -c dev
此时情况为

产生了两条分支,我们再回到master分支,用 git merge dev
对dev分支进行汇总
当前我们已经创建好冲突了,那么该如何解决呢
上图已经提示,冲突再hello.md这个文件里,那我们进入文件,发现此时文件已经变为
<<<<<<<< 表示当前分支也就是master
**************************************** 表示为分隔符
>>>>>>>> 表示为另一分支也就是dev
此时我们可对冲突内容进行修改
在进行提交操作
git add .
git commit -m '冲突已解决'
git merge dev
此时分支情况为

dev停留在了原处,master已经指向版本五了,此时已经完成冲突的解决了,再再次卸磨杀驴git branch -d dev
(为了后续分支策略图好看一点)
4.4 分支策略
默认情况下git在合并时会采用Fast forward的分支管理策略,像在将分支之前所有的合并方式,都是Fast forward,这样做的后果就是,省略了合并步骤
比如版本一到版本二,版本二到版本三就是用Fast forward,这样做的话,优点是简单明了,而缺点就是看不出合并痕迹
打个比方,版本四到版本五我们知道提交了新的commit,但是我们不知道是不是从其他分支merge后得到的结果,还是自己修改后add+commit得到的结果
(上图版本四到版本五是我根据逻辑画出来的,git在展示时,还是按照版本一到版本二那样展示可输入git log
查看)
而为了能看出合并痕迹,我们可以在merge时使用 –no-ff参数
做个简单的对照测试
我们创建并切换到dev分支,随便写一行
add+commit后再切换回master,调用如下命令:
git merge
git log --graph --pretty=oneline --abbrev-commit
上图只是出现新的commit,但是不知道是否进行了合并操作
同样步骤只是命令不同,做个对照实验:
git merge --no-ff -m '不用Fast forward' dev
git log --graph --pretty=oneline --abbrev-commit
因为本次merge会创建一个新分支,所以加一个-m参数
可以看出我们在ed6aa53到b152f62时处有一个合并操作(红色与绿色分支合并)
4.5 保存工作区
当前正在dev分支上写代码,突然来了个bug – bug01,正常情况下会在主分支基础上创建一个bug01的分支然后改bug,但是当前分支上的功能还没实现完,工作区内的文件还没法提交,所幸git提供了隐藏当前工作区修改内容的功能,在命令行输入以下命令
git stash
git status
发现dev分支上的内容已经给保存起来了,当前工作区已经回到了初始状态
当我们修改完bug后,需要让工作区回退到一开始的状态,我们可以输入git stash list
查看隐藏起来的内容
此刻只有**stash@{0}**这一个stash,你可以输入多次git stash
来保存多个stash
此时我们只是查到了有一个stash,而恢复stash的方式有两个:
-
先恢复stash,再手动删除stash
git stash apply stash{0} git stash drop stash{0}
-
恢复并删除stash
git stash pop
仔细想一想,如果有master上有bug的话,那建立在master分支只上的dev分支不也有相同的bug,那再同样的操作做一遍不就显得很麻烦了,git提供一个命令git cherry-pick 版本号
可以克隆一个特定的commit到当前分支,这样就不需要再在当前分支修改一遍bug了
4.6 多人协作
一般公司远端仓库有一个master分支和若干个dev分支,我们完成任务后上传时,可以通过git push origin 分支名
上传到指定分支,但并不是所有分支都需要上传到远端仓库的,比如改bug分支(你能告诉你老板你写了多少bug吗)
当你首次克隆代码到本地时,默认只有master分支,你可以创建本地dev分支,并且绑定到远端dev分支上
git checkout -b dev origin/dev
当你完成commit,打算push到dev分支上时,git可能会提示有冲突
解决方法:
使用git pull
先将代码拉取到本地,合并后再推送,如果git pull
也失败了,说明你没有将本地dev与远端dev做关联,可以使用以下命令做关联后,再推送
git branch --set-upstream-to=origin/dev dev
解决冲突方式参考4.3
4.7 Rebase
主要作用有二:
- 分支合并
- commit改写
4.7.1 分支合并
首先我们目前项目状态是这样的

当前HEAD指针指向master分支,且master分支指向当前版本,我们在这个版本的基础上,创建一个dev分支

而后我们在master分支上前进一个版本

dev分支再git merge master
也前进了一个版本,那结果就变成这样

dev是从master分支分出来的,随后又同步了master分支的改动,可以说dev版本是没什么必要的
为了整理分支
首先先回到dev分支merge前的状态,也就是

然后再在dev分支上使用git rebase master
,此刻就变成了

对比rebase与merge的结果可以发现简洁了不少
4.7.2 commit次数改写
当前项目环境如下

此刻dev再向前commit3次,然后这3次commit都是修改了同样的一个接口
因为修改的是相同的内容所以说: master版本 -> 版本A -> 版本B- > 版本C
其实等价于 master版本 -> 版本C
如果3次还好,如果是30次,那整个分枝树就会特别的冗长
做一个测试,我将hello.md添加了3次内容,并且commit了3次
此时我们可以通过git rebase -i HEAD~n
来指定合并多少次commit
git log
查看commit记录

我们将最近三个版本合并,则可以输入
git rebase -i HEAD~3
-i是进入交互模式,页面展示内容如下:
1 p f07fc36 A
2 s 8e65a18 B
3 s bd26f6f C
4
5 # Rebase 044f0cc..14c0a82 onto 044f0cc (3 commands)
6 #
7 # Commands:
8 # p, pick <commit> = use commit
9 # r, reword <commit> = use commit, but edit the commit message
10 # e, edit <commit> = use commit, but stop for amending
11 # s, squash <commit> = use commit, but meld into previous commit
12 # f, fixup <commit> = like "squash", but discard this commit's log message
13 # x, exec <command> = run command (the rest of the line) using shell
14 # b, break = stop here (continue rebase later with 'git rebase --continue')
15 # d, drop <commit> = remove commit
16 # l, label <label> = label current HEAD with a name
17 # t, reset <label> = reset HEAD to a label
18 # m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
19 # . create a merge commit using the original merge commit's
20 # . message (or the oneline, if no original merge commit was
21 # . specified). Use -c <commit> to reword the commit message.
22 #
23 # These lines can be re-ordered; they are executed from top to bottom.
24 #
25 # If you remove a line here THAT COMMIT WILL BE LOST.
26 #
27 # However, if you remove everything, the rebase will be aborted.
注:
- 第一行到第三行为主要操作内容,从左至右分别是命令-> commit号 -> 版本备注
- 后面都是备注,第七行到第十八行为命令说明
- 我们选择需要执行的命令,我们选择了s与p,s的内容为合并前一个内容并提交
- 保存提交后可能会报错
error: cannot 'squash' without a previous commit
,原因是不能合并已提交到远端仓库的版本
此时会保存退出后会展示以下内容

再次输入git log

发现ABC三个版本已经合并为一个了
命令汇总
查看分支: git branch
创建分支: git branch 分支名
或 git checkout 分支名
删除分支: git branch -d 分支名
强制删除分支(用于分支代码未整合到主分支时使用): git branch -D 分支名
切换分支: git checkout 分支名
或 git switch 分支名
创建并切换分支:git checkout -b 分支名
或 git switch -c 分支名
合并某分支内容到当前分支: git merge 分支名
或 git rebase 分支名
修改commit次数: git rebase -i HEAD~n
保存工作区: git stash
查看stash: git stash list
恢复stash: git stash apply stash{0}
删除stash: git stash drop stash{0}
恢复并删除stash: git stash pop
将将其他分支修改内容同步到本分支: git cherry-pick 版本号
本地分支关联远程分支: git branch --set-upstream-to=origin/dev dev
5 标签管理
5.1 标签的创建
当我们提交一次commit后,会生成相应的commit号,不过都是一些无规则的数字字母,不方便记忆和查找,就比如我想回滚到某一个版本,我需要先git log
查卡该版本的commit号,之后再通过git reset --hard 版本号
回滚回去,随着commit次数的增多,查找难度也会越来越大,这个时候,为了能够方便记忆与查找,出现了一种打标签的技术
下图是最新的一次提交记录

打标签还是十分简单的,我们可以通过下述方式给最新的版本打上标签
- 切换到需要打标签的分支上
- 使用命令
git tag 标签名
- 打完标签后可以通过
git tag
来查看所有标签
但这只是给最新版打标签,如果我想要给上周的版本打标签得按照以下步骤:
git log --pretty=oneline --abbrev-commit
查出版本号git tag 版本号 commit 号
给指定commit号的版本打上标签

注意项:标签排序是按照字符串顺序排序,与版本先后顺序无关
还可以通过git tag -a 标签名 -m "备注内容" commit号
给标签打上备注
其中 -a指定标签名 -m指定备注内容,commit号跟在最后
还可以通过git show 标签名
来查看具体内容

5.2 标签推送
当标签创建完成后,可以通过git push origin 标签名
将单个标签上传到远端仓库
也可以通过 git push origin --tags
将全部标签上传
5.3 标签的删除
当然,人有失手,马有失蹄,如果不小心打错标签了也是可以删除的
如果只是在本地,还没push到远端仓库,那可以使用git tag -d 标签名
来删除标签
如果还将标签推送到远端仓库的话,还需要使用git push :refs/tags/标签名
来删除
命令汇总
查看所有标签: git tag
给最新版本打标签: git tag 标签名
给指定版本打标签: git tag 版本号 commit 号
查看标签详情: git show 标签名
推送单个标签: git push origin 标签名
推送所有标签: git push origin --tags
删除本地仓库的标签: git tag -d 标签名
删除远端仓库的标签: git push :refs/tags/标签名