一、 Git 简介
1. Git 诞生
Linus在1991年创建了开源的Linux,此后Linux系统不断发展,已经成为最大的服务器系统软件了
很长一段时间内,Linux的代码管理:由世界各地的志愿者把源代码文件通过diff的方式发给Linus,然后由Linus本人通过手工方式合并代码(虽然一些商用的版本控制系统比 CVS、SVN 等好用,但Linux没有使用,这与Linux的开源精神不符)
随着 Linux系统 的发展,其代码管理由人工手动合并难以维持;这时,BitKeeper的东家BitMover公司出于人道主义精神,授权Linux社区免费使用这个版本控制系统
2005年,双方合作局面被打破;原因是:Linux社区牛人聚集,试图开发Samba的Andrew试图破解BitKeeper的协议,BitMover公司怒了,要收回Linux社区的免费使用权。
Linus花了两周时间自己用C写了一个分布式版本控制系统,这就是Git!一个月之内,Linux系统的源码已经由Git管理了
Git迅速成为最流行的分布式版本控制系统,尤其是2008年,GitHub网站上线了,它为开源项目免费提供Git存储,无数开源项目开始迁移至GitHub,包括jQuery,PHP,Ruby等等
2. Git 是一个 分布式版本控制系统
3. 集中式 和 分布式版本控制系统的 区别
集中式 版本控制系统:
版本库是集中存放在中央服务器的,工作时,先从中央服务器取得最新的版本;工作后,再把代码推送给中央服务器
弊病:传输速度相对较慢;需要联网才能工作(不联网不能提交等操作)
常见的集中式版本控制系统:CVS、 SVN
分布式 版本控制系统:
分布式 版本控制系统 没有中央服务器,每个人的电脑上都是一个完整的版本库;不联网即可完成代码提交等操作
分布式版本控制系统,通常也有一台充当 “中央服务器” 的电脑;协同办公时,将各自代码推送到远程,其他开发人员可将远程最新代码拉取到本地仓库
常见的分布式版本控制系统:Git
4. 常用的版本控制对比
VSS:
微软开发的版本控制系统,全程
Visual Source Safe
;作为 Microsoft Visual Studio 的一名成员,它主要任务就是负责项目文件的管理;早期扛起了版本管理系统方面的大旗;能帮助解决一部分版本控制方面的问题,也在一定程度上帮助解决代码共享方面的难题
不足:没有分支功能;同一文档在同一时间内只允许一人修改;文件存储,服务器必须共享文件夹,对文件的安全性没有足够保障
SVN:
全称:Subversion;集中式版本控制系统;是一个开放源代码的版本控制系统,相较于RCS、CVS,它采用了分支管理系统,它的设计目标就是取代CVS
革新:提供了分支的功能,从而解决了VSS文件独占的问题,提供开发人员的工作效率
不足:必须联网,如果不能连接到服务器上,基本上不可以工作(不能提交,还原,对比等等)
Git:
分布式版本控制系统,适合分布式开发,每一个个体都可以作为服务器(即一个完整的版本库);每一次Clone就是从远程服务器上pull到了所有的内容,包括版本信息
革新:操作速度快;可离线工作;远程仓库维持这一个原始版本库(杀手锏之一),每个开发人员本地有自己的版本库,开发人员在不联网的情况下,即可工作,在本地版本库进行提交、还原、对比等操作
版本的分支(branch)和合并(merge)十分方便
二、 Git 相关概念
1. 版本库、版本、版本号
版本库:又称仓库(repository)
可以把其简单的看成一个目录(文件夹);这个目录下的文件都由Git管理,当我们对文件进行修改、删除等操作,Git都会对其进行跟踪
Git的版本库里存了很多东西:其中最重要的就是称为stage(或者叫index)的暂存区,还有Git为我们自动创建的第一个分支
master
,以及指向master的一个指针叫HEAD
版本:对版本库中的文件进行修改,并已提交日志的方式(相关Git提交指令)让Git记录下此次操作,就是一个版本
版本号:每一个版本的唯一标识;每次提交日志,Git都会生成一个版本号;和SVN不一样,Git的commit id不是1,2,3……递增的数字;而是一个SHA1计算出来的一个非常大的数字,用十六进制表示
**当前版本:**Git中,用
HEAD
表示当前版本;HEAD
是指向master
分支的指针
2. 工作区、暂存区
工作区:简单理解为,我们工作时所在的目录(文件夹)
暂存区:
Git版本库中,暂时存放修改的文件的区域
Git和其他版本控制系统如SVN的一个不同之处就是有暂存区的概念
工作区 、暂存区 、版本库的对应关系:
三、获取 Git 仓库
方式一:在现有文件夹中 Git 初始化 git init
- 现有文件夹中,执行
git init
后,版本库中会创建一个文件.git
;这个文件是Git来跟踪管理版本库的;千万不要动
方式二:克隆远程 Git 仓库 git clone 远程仓库地址
命令: 默认本地仓库名字
git clone https://github.com/ZXvictory/myTest.git
命令: 自定义本地仓库名字;
git clone https://github.com/ZXvictory/myTest.git 自定义仓库名
四、Git 在本地仓库 记录每次更新
第一步:查看 仓库中被改动文件的 状态:git status
状态1:未放到暂存区的文件(下图:红色部分)
状态2:放到暂存区 且 未被提交的文件(下图:绿色部分)
(1) 查看文件变化:尚未暂存的文件 git diff
(2) 查看文件变化:已暂存 将要添加到下次提交的文件 git diff --cahced
(3) 一键还原的命令 git checkout -- 文件
:三种情况,如下
情况1:工作区的文件被修改,但没推到暂存区,执行
git checkout -- 文件
,会撤销修改,回到和版本库一模一样的状态情况2:文件已添加到暂存区,但又被修改,执行
git checkout -- 文件
,会撤销修改,回到添加到暂存区后的状态情况3:将暂存区 或 仓库上的文件被删除,执行
git checkout -- 文件
,可还原删除的文件;若文件没有被暂存,不能还原
(4) 未暂存的的文件最好 及时 暂存/提交;否则一旦删除或修改,将无法撤回
工作区 和 版本库的文件 最好保持一致;举例说明:版本库中 处于暂存区 或
master
分支上的一个文件,在本地(工作区)手动删除,即造成:工作区中没有此文件;版本库中有此文件;此时,处理方案有两种:方案一:在版本库中,删除该文件
git rm 文件
方案二:执行
git checkout --文件
还原工作区中删除的文件
第二步:将有变动的文件 放到 缓存区:git add *
、 git add -A
添加单个文件到暂存区:
git add 单个文件
添加所有文件到暂存区:
git add *
、git add -A
执行上面的命令,没有任何显示,这就对了,Unix的哲学是 “没有消息就是好消息”,说明添加成功;再查看文件状态如下
第三步:将缓存区文件 提交到 本地仓库:git commit -m "日志"
执行完以上第一、二、三步后,意味着 将版本库中文件的修改,提交到了本地仓库的当前分支上了
图示步骤如下:
将有改动的文件添加到暂存区
将暂存区文件提交到本地仓库
五、 Git 日志 与 版本回退
1. 查看详细日志 git log
2. 查看简要日志(常用) git log --oneline
3. 查看详细版本号日志 git log --pretty=oneline
4. 查看所有日志(含回退前的日志) git reflog
5. 版本回退 git reset --hard 版本号前几位
、 git reset --hard HEAD^
当前版本:用
HEAD
表示方式一:
git reset --hard HEAD^
表示退回上一个版本;- 实际上有几个
^
就退回几个版本;退回到前100个版本的命令:git reset --hard HEAD~100
方式二:
git reet --hard 版本号
其中版本号前几位即可,Git会自动去找
6. 版本回退后 强行上推 git push -f origin master
注意:
本地版本回退后,如果执行
git push origin master
推到远程;结果是 推不上去,提示 远程代码 比 本地代码 新,建议 先pull
再push
这时候执行
git push origin master
可以将本地代码 强行 推到线上;线上代码 更新后会和本地代码一致
六、 Git 分支操作
1. Git 分支开发工作流
- 什么是分支:
- 每次提交,Git都把它们串成一条时间线,这条时间线就是一个分支
HEAD
总是指向当前分支
2. Git 分支 常见操作
(1) 查看所有分支(当前分支前面有 * 表示) git branch
(2) 查看本地分支 和 所有远程分支 git branch -a
(3) 创建分支 git branch 分支名
(4) 删除 本地分支 git branch -d 分支名
、 删除 远程分支 git push origin --d 分支名
前提: 在所在分支上不能删除本分支;需要切换到零一个分支上,再删除要删除的分支
指令:
删除 本地分支:
git branch -d 分支名
强行 删除本地分支:
git branch -D 分支名
- 分支上修改后,没合并到主分支,删除会报错,需强行删除本地分支
删除 远程分支:
git push origin --d 分支名
(5) 切换分支 git checkout 分支名
(6) 创建 并 切换分支 git checkout -b 分支名
(7) 分支 合并: 指定分支 到 当前分支 git merge 分支名
、 git merge --no-ff 分支名
(推荐使用) 【重要】
快进式 合并:
命令 格式:
git merge 分支名
解释:
如果是将
dev
合并到master
上,执行git merge 分支名
命令,Git 会直接将Master
分支指向dev
分支合并代码,不会出现提交日志(实际上是 分支指向)
结果:
master
分支上代码更新,但没有提交记录(没有dev
分支上的日志提交记录)
创建新节点 合并:(项目中 推荐使用)
命令 格式:
git merge --no-ff 分支名
解释:
如果是将
dev
合并到master
上,执行git merge --no-ff 分支名
命令,Git 会在Master
上创建一个新的节点,然后dev
分支 指向Master
分支创建的新节点合并代码,会出现日志提交(默认形式 或 自定义)
结果:
master
分支上代码更新,同时会dev
分支上的日志提交记录
(8) 分支 变基 — 整合来自不同分支 代码 的另一种形式 【重要】
变基:使得提交历史更加整洁;这两种整合方法的最终结果没有任何区别
实践: 若 rebase 前(合并 commit 前)已向远程仓库提交过该分支:则需先在远程仓库删除之前提交的分支后,再rebase 、commit
对别 merge 与 rebase:
场景需求: 需要将
dev
分支上的内容,整合到master
分支上git merge --no-ff dev
命令:在
master
分支上 整合过程: 将两个分支的祖先节点、各自的最新节点,三个节点整合;在 分支链的 最顶端 创建一个新节点master
上 整合后的节点顺序是:祖先节点
—>master 分支上祖先节点之后 master 提交的 commit
—> merge 后的最新提交节点
git merge rebase dev
命令:在
master
分支上 整合过程: 将master
分支上祖先节点之后master
提交的 commit 抽离出来;祖先节点之后 指向dev 分支上提交的节点
;将master
分支上祖先节点之后master
提交的 commit,置于dev 分支上提交的节点
(可合并 commit)master
上 整合后的节点顺序是:祖先节点
—>dev 分支提交的节点
—>aster 分支上祖先节点之后 master 提交的 commit(可合并 commit)
代码冲突:merge 与 rebase 不同的解决策略
merge:遇见冲突后会 直接停止(手动解决冲突);重新提交 commit 后,才能完成 merge
rebase:遇见冲突后会 暂停当前操作(手动解决冲突)
处理一:解决完冲突后
git rebase --continue
继续 rebase处理二:直接
git rebase --skip
跳过冲突,强行 rebase(当前分支的修改会直接覆盖目标分支的冲突部分)处理三:直接
git rebase --abort
停止此次 rebase 操作
七、 Git 分支管理策略 及 工作场景
1. Git 分支管理策略
(1) 主分支 master
代码库应该有一个、且仅有一个主分支。所有提供给用户使用的正式版本,都在这个主分支上发布, 即 用来分布重大版本
Git主分支的名字,默认叫做Master。它是自动建立的,版本库初始化以后,默认就是在主分支在进行开发
(2) 开发分支 dev
- 这个分支可以用来生成代码的最新隔夜版本(nightly)。如果想正式对外发布,就在Master分支上,对Develop分支进行”合并”(merge),下图为master分支多次合并dev的图示
(3) 功能分支 feature(临时)
为了开发某种特定功能,从Develop分支上新建 feature 功能分支;开发完成后,再将 feature 分支合并到 develop 分支上
命名:可以采用
feature-*
的形式测试稳定后,删除 feature分支,将 develop 分支合并到 master 分支上
git checkout -b feature-x develop
git checkout develop
git merge --no-ff feature-x # 开发完成后,将功能分支合并到develop分支
git branch -d feature-x # 删除feature分支
(4) 预发布分支 release(临时)
发布正式版本之前(即合并到Master分支之前),我们可能需要有一个预发布的版本进行测试
预发布分支是从Develop分支上面分出来的,预发布结束以后,必须合并进Develop和Master分支
命名:可以采用
release-*
的形式
git checkout -b release-1.2 develop
git checkout master
git merge --no-ff release-1.2 # 确认没有问题后,合并到master分支
git tag -a 1.2 # 对合并生成的新节点,做一个标签
git checkout develop
git merge --no-ff release-1.2 # 再合并到develop分支
git branch -d release-1.2 # 删除预发布分支
(5) bug分支(临时)
软件正式发布以后,难免会出现bug。这时就需要创建一个分支,进行bug修补
修补bug分支是从Master分支上面分出来的;修补结束以后,再合并进Master和Develop分支
命名:可以采用
fixbug-*
的形式
git checkout -b fixbug-0.1 master
git checkout master
git merge --no-ff fixbug-0.1 # 修补结束后,合并到master分支
git tag -a 0.1.1
git checkout develop
git merge --no-ff fixbug-0.1 # 合并到develop分支
git branch -d fixbug-0.1 # 删除"修补bug分支"
2. 解决:分支冲突
多人分别在自己的分支上,开发同一个文件中的代码,在合并时可能会造成分支冲突
合并分支,若有冲突,会出现如下提示:
Git用
<<<<<<<
,=======
,>>>>>>>
标记出不同分支的内容,如下图;需要手动解决冲突
3. 解决:紧急bug
场景:在自己的开发分支上,代码写到一半,需要要解决紧急bug;但有不想提交现在的代码,因为还没开发完
解决:此时,可以将开发分支上的代码储藏起来
git stash
;现在工作区是干净的,不会影响解决bug;可以看看储藏区git stash list
,刚才的代码在 储藏区里了;解决完bug后,需要恢复储藏的代码,有两个命令:命令一:
git stash apply
;此时代码已恢复,但储藏区的内容并没有清除,需要git stash drop
手动清除命令二:
git stash pop
;恢复代码的同时,把储藏区的内容也删除了
4. 开发:不确定的功能
场景:*master上引出一个分支,来开发新功能;开发完了但还没合并*到其他分支上;此时需求不要了,需要删除新分支,执行
git branch -d 分支名
;会报错error: The branch 'feature' is not fully merged.
解决:此时可以使用
git branch -D 分支名
;来强行删除新分支