GIT
什么是Git?
Git是一个版本控制器:可以记录工程的每一次改动和版本迭代的一个管理系统
注意事项:
- 所有的版本控制系统,其实只能跟踪文本文件的改动(如TXT文件、网页、所有的程序代码等),版本控制系统可以告诉你每次的改动,如第几行增加/删除了某些单词等
- 而图片、视频等二进制文件,虽然也可以由版本控制系统管理,但是无法跟踪文件的改变,只能知道文件大小的变化(从100K -> 200K等操作)
Git的安装
- Linux-centos
查看版本:
git --version
安装Git:
sudo yum -y install git
- Linux-ubantu
查看版本:
git --version
安装Git:
sudo apt-get install git -y
Git 的基本操作
创建本地仓库:
git init
切记: 不要在.git文件中进行操作
配置Git:
配置用户名
git config [–global] user.name “昵称”
配置邮箱
git config [–global] user.email “邮箱格式”
注意: 执行命令必须在仓库中, --global是可选项,作用是把配置应用到当前机器的所有仓库中,而要删除全局配置也需要加上–global选项
查看配置命令:
git config -l
删除对应的配置命令:
git config [–global] --unset user.name
git config [–global] --unset user.email
认识工作区、暂存区、版本库
工作区: 电脑上要存放代码或者文件的目录
暂存区: 英文名(stage/index)一般存放在 .git目录下的index文件中,有时候也把暂存区叫做索引
版本库: 又叫仓库(repository),工作区有一个隐藏目录 .git,它不算工作区,而是Git的版本库,这个版本库里的文件都可以被Git管理起来,每个文件的修改、删除,Git都能跟踪,以便任何时刻都可以追踪历史,或者在将来的某个时刻可以“还原”
- 工作区和版本库是处在同一个文件目录下的
- 在创建Git版本库时,Git会为我们自动创建一个唯一的master分支,以及指向master的一个指针HEAD
- 当对工作区修改的文件执行
git add
命令时,暂存区目录树的文件索引会被更换 - 当执行
git commit
时,master分支会作相应的更新(暂存区的目录树才会被真正写到版本库中)
添加文件
添加一个或多个文件到暂存区:
git add [文件1] [文件2] …
添加指定目录到暂存区,包括子目录:
git add [目录]
添加当前目录下所有文件的改动到暂存区:
git add .
提交暂存区全部内容到本地仓库:
git commit -m “message”
提交暂存区的指定文件到仓库区:
git commit [文件1] [文件2] … -m “message”
注意: git commit
后面的-m
选项,要跟上描述本次提交的message,由用户自己完成,此部分内容不可省,要好好描述,用来记录提交细节,git commit
命令执行成功后会告诉我们改动结果
查看历史提交记录:
git log
该命令从近到远的显示提交日志,并且可以看到我们commit时的日志消息,如果觉得输出的信息太多,可以加上–pretty=oneline参数:
git log --pretty=oneline
查看.git文件
tree .git/
index
就是暂存区,add后的内容都是添加到这里的HEAD
是默认指向master分支的指针refs/heads/master
文件里保存了当前master分支的最新commit idobjects
是git的对象库,包括了创建的各种版本库对象以及内容
当执行git add
命令时,暂存区的目录树被更新,同时工作区修改的文件内容被写入到对象库中的一个新的对象中,位于“.git/objects”目录下,查找object时要将commit id分成2部分,前2位是文件夹名称,后38位是文件名称,找到这个文件之后,一般不能直接看到里面是什么,该文件是经过sha(安全哈希算法)加密过的文件,我们可以通过
git cat-file -p
命令来查看版本库对象的内容。
注意: Git跟踪并管理的是修改(新增、删除等),并不是文件
查看当前仓库的状态:
git status
该命令用于查看上次提交之后是否对文件再次进行修改
显示暂存区和工作区文件的差异:
git diff [文件名]
显示版本库和工作区文件的区别:
git diff HEAD --[文件名]
版本回退
语法:
git reset [–soft | --mixed | --hard] [HEAD]
HEAD
- 可直接写成
commit id
,表示指定退回的版本 HEAD
表示当前版本HEAD^
上一个版本HEAD^^
上上一个版本- 以此类推……
- 可直接写成
- 可以使用~数字表示:
HEAD~0
当前版本HEAD~1
上一个版本HEAD~2
上上一个版本- 以此类推……
--soft
只回退版本库,工作区和暂存区不回退--mixed
回退版本库和暂存区,不回退工作区--hard
回退工作区、暂存区、版本库到指定版本
打印历史日志(可以补救回退之后找不到回退之前的版本ID):
git reflog
虽然这里面只是版本号的一部分,不过依然可以用来回退到相对应的版本
撤销修改
如果在工作区 写了很长是时间的代码,但是觉得不妥,想恢复到上一个版本(大前提:都未把本地仓库push到远程仓库)
-
工作区的代码,未进行add
手动删除:可以直接删除(通过对文件内容的操作)
使用:
git checkout – [文件名]
让工作区回到最近一次add或者commit时的状态,其中–不可省
-
已经add,但未commit
使用
git reset [--mixed(默认参数,可以省略)] HEAD [文件名]
-
已经add,并且也已经commit了
直接通过
git reset --hard HEAD^
回退版本
删除(版本库中)文件
-
逐步操作
rm [文件名] “删除工作区”
git add
git commit
-
工作区和暂存区合并操作
git rm [文件名] “该命令会直接把工作区和暂存区都删除”
git commit
分支管理
理解分支
Git会把每次的(提交)操作串联成一条时间线,这条时间线可以理解成为是一个分支,每次提交,master分支都会向前移动一步,随着不断的提交,master分支的线也会越来越长,而HEAD指针只要一直指向master分支即可指向当前分支
创建分支
Git支持创建其他分支,我们创建一个自己的分支dev
分支命令:
git branch #查看当前本地分支
git branch [分支名,这里是dev] #新建分支dev
当我们创建新的分支之后,Git新建了一个指针叫dev,*
表示当前HEAD
指向的分之是master
分支,刚创建好dev分支之后,会发现当前dev和master指向同一个修改
切换分支
切换命令:
git checkout [分支名]
此时,HEAD指向dev,表示我们切换到了dev分支上
、
合并分支
注意: 合并分支要在主分支(要合并到的分支)上操作,如dev合并到master上,需要在master分支下操作(合并)
命令:
git merge [被合并的分支名,此处是dev]
合并后状态如图,master就可以看到dev分支提交的内容
Fast-forward代表“快进模式”,也就是直接把master指向dev的当前提交,所以合并速度很快
删除分支
合并完分支之后dev分支对我们来说就没什么用了,那么dev分支就可以被删除了
注意: 删除分支的时候不能处于要被删除的分支之下操作
删除分支命令:
git branch -d(delete) [分支名]
因为创建、合并和删除分支非常快,所以Git鼓励使用分支完成某个任务,这和直接在master分支上工作效果一样,但是过程很安全。
合并冲突
实际的合并的时候,可能会出现代码冲突问题
情景:在master单分支下创建一个新分支dev1,并切换至dev1,在dev1分支下对ReadMe文件修改(末尾一行改为bbb)并进行提交,切换回master分支,发现ReadMe文件并未修改(末尾一行是文件原本内容aaa),此种情况很正常,之后对ReadMe文件再进行修改(末尾一行的aaa改为ccc)并进行提交,此时在master分支下的master文件末尾内容是ccc,在dev1分支下ReadMe文件末尾是bbb,然后在master分支下合并master分支和dev1分支,会出现合并冲突
此种情况,Git只能试图把各自的修改合并起来,但是这种合并就会有冲突
报错:Automatic merge failed; fix conflicts and then commit the result.
发现ReadMe文件有冲突后,可以直接查看文件内容
Git会用<<<<<<<,=======, >>>>>>>
来标记出不同分⽀的冲突内容
……
<<<<<<< HEAD
write ccc for new branch
=======
write bbb for new branch >>>>>>> dev1
此时我们需要手动调整冲突代码,并提交(再次提交很重要,切勿忘记)
冲突解决完之后状态为:
最后,不要忘记dev1分支使用完毕后可以删除
创建分支并且切换到新创建的分支上:
git checkout -b [新创建的分支名]
通过log命令来查看分支合并情况(图示法)
git log --graph [–pretty=oneline] --abbrev-commit
分支管理策略
对于Git合并分支时会有两种模式
- 模式一:
Fast forword(ff模式)
在此模式下,删除分支后,查看分支历史时,会丢掉分支信息,看不出最新提交 到底是merge进来的还是正常提交的
- 模式二:
非Fast forword(no-ff模式)
此模式下,可以从分支历史中看出分支信息
Git支持我们强制禁止Fast forword
模式,那么我们在merge时生成一个新的commit,这样从分支历史上可以看出分支信息
强制禁止Fast forword
模式(也就是在合并时伴随着再次提交):
git merge [–no-ff] [-m “日志信息”] [要被合并的分支名]
注意: --no-ff参数,表示禁用Fast forword模式,此时会创建一个新的commit,所以加上-m参数,把描述写进去。
此时状态为:
所以合并分支时,加上--no-ff
参数就可以使用普通模式合并,合并后的历史有分支,能看出曾经做过合并,而Fast forword合并则看不出曾经做过合并
分支策略: 实际开发中,应该按照master分支非常稳定
来发布新版分,也就是说,不能再master分支上写新代码,而要在dev分支上开发新功能,到一定程度再把dev分支合并到master分支上
bug分支
如果在dev2分支上开发到了一半,发现master分支上有bug,需要解决。在Git中,每个bug都可以通过一个新的临时分支来修复,修复后,分支合并,然后将临时分支删除,但是此时dev2的代码工作区已经开发了一半,暂时无法提交怎么办?
Git提供了git stash
命令:
git stash #将当前工作区信息进行储藏,被储藏的内容可以在将来的某个时间恢复
储藏dev2工作区之后,由于我们要基于master分支修复bug,所以需要我们切回master分支,新建fix_bug分支进行修复bug操作,当bug修复完成,继续切回dev2分支进行开发,但是此时工作区是干净的,我们需要使用:
git stash pop #来恢复储藏信息到dev2工作区
git stash list #查看储藏信息
#注意:
git stash apply #恢复现场,但是refs下的stash内容并不删除
git stash drop #配合上方命令主动删除stash内容
#可以使用以下命令恢复指定的stash:
git stash list #查看stash目录
git stash apply stash@{0}
注意: 储藏的信息是在refs下保存,当我们使用git stash pop
恢复储藏之后,会自动删除refs下的stash内容
修复完bug之后的合并方式:
修复完bug之后,并未在dev2上显示,也就是说master分支的最新提交是要领先于新建dev2时master的状态,所以我们在dev2下看不到修复的bug的相关代码
- 直接将开发分支合并到master分支上(会出现代码冲突,解决冲突容易出错)
- master分支先合并到开发分支上,后再合并到master分支上(将代码冲突的解决放到开发代码分支上处理)
删除临时分支
一般我们在开发代码的时候都是新建一个分支,在新建的分支上操作,但是如果此时在新建分支上开发代码到一般情况下,被项目经理叫停说不开发这个功能了,虽然白干了,但是这个新建的分支(已经add和commit)还得销毁,这时git branch -d
命令(只能删除未进行add和commit操作的分支)删除不了分支
强制删除分支:
git branch -D [分支名]
远程仓库
Git是分布式版本控制系统 ,上面的介绍都是在本地,那么分布式如何理解?
就是找一台24小时运行的电脑,作为一个充当服务器角色,所有人都从这个“服务器”(github或者gitee)仓库克隆(拉取)一份到自己的电脑上,并且各自把各自的提交推送到服务器仓库里,也从服务器仓库中拉取别人的提交。
克隆远程仓库:
git clone [远程仓库的地址]
克隆地址有两种方式:
- HTTPS方式(每次推送需要输入口令)
- SSH方式(每次推送不需要输入口令)
https简单方便,SSH方式需要在远端库中配置公钥(否则会被服务器拒绝clone链接)
SSH方式的公钥和私钥生成方式:
如果在.ssh目录下没有id_rsa
和id_rsa.pub
这两个文件,就是没有生成公钥和私钥
ssh-keygen -t rsa -C [“自己的邮箱(就是在gittee上面的邮箱)”]
一路回车,然后去.ssh
文件夹里找id_rsa
和id_rsa.pub(公钥)
这两个文件
Github/Gitee允许添加多个公钥
当从远程仓库克隆后,实际上Git会自动把本地的master分支和远程的master分支对应起来,并且,远程仓库的默认名称是origin
在本地可以使用
git remote #查看远程库的信息
git remote -v #显示更详细的信息
向远程仓库推送
命令:
git push [远程主机名] [本地分支名]:[远程分支名]
git push [远程主机名(origin)] [本地分支名] #当本地分支名和远程分支名一致时可省略远程分支名
注意:
在推送前需要本地设置的name和email和gitee上配置的用户名和邮箱一致,否则会出错
拉取远程仓库
命令:
git pull [远程主机名(origin)] [远程分支名]:[本地分支名]
git pull [远程主机名(origin)] [远程分支名] #当远程分支名和本地分支名一致时,可以省略本地分支名
配置Git
忽略特殊文件
日常开发中,有些文件不想或者不应该提交到远端,比如:保存了数据库密码的配置文件,那么怎么让Git知道呢?
在Git的工作区的根目录下创建一个特殊的.gitignore
文件,然后把要忽略的文件名填进去,Git就会自动忽略这些文件。
-
.gitignore
文件可以在gitee创建仓库的时候自动生成(勾选操作) -
但是如果没有选择,也可以创建一个
如果要忽略以.so
和.ini
结尾的所有文件:
#省略选择模本的内容
……
#My configurations:
*.so
*.ini
.* #排除所有.开头的隐藏文件
!XXX(文件名).so(文件后缀)
在.gitignore
文件中也可以指定某个确定的文件
当某个文件在我们要忽略的配置文件中,但是却要强制添加:
git add [-f] [文件名] #在.gitignore被忽略后,强制添加到git管理中
git check-ignore [-v] [文件名.后缀] #查看某个文件在.ignore文件中的配置
给命令配置别名
使用Git的时候有些命令太长了,我们可以把它简化:
比如将git log --pretty=oneline --abbrev-commit
简化为git lpa
:
git config --global [alias.lpa(简化后的命令)] [‘log --pretty=oneline --abbrev-commit’(简化前的命令)] #–global是对该电脑下的所用仓库适用,如果不加,就只是对当前仓库起作用
注意简化的只是不包含git这个开头关键字之后的命令
标签管理
理解标签
标签tag
可以简单的理解为对某次提交的一个标识,相当于是起了别名,比如针对某个版本的最后一个提交起一个v1.0
这样的标签来标识里程碑的意义
相较于本次提交的commit id,tag很好的代替了它
创建标签
git tag [标签名] #(默认)给最新一次的提交打上标签
git tag #查看所有标签
git tag [标签名] [某次cimmit id] #给某个历史提交的commit id打上标签
git show [标签名] #查看标签信息
git tag -a [标签名] -m “XXX” [commit id] #给某次历史提交的commit id打上标签并且给上说明 -a指定标签名 -m指定说明文字
注意: 标签不是按照时间排序的,而是按照字母进行排序,而且创建完标签,在refs下问有一个tag,tag下存的是标签,但是标签里面存的是commit id
操作标签
删除标签:
git tag -d [标签名] #删除标签
git push [origin(远程分支名)] <标签名> #向远程分支推送标签(单个)
git push [origin(远程分支名)] --tags #向远程分支推送标签(全部)
要是想删除掉远程仓库中的标签,需要先把本地的删除掉,然后再推送到远程仓库:
git tag -d [标签名] #1.先删除本地仓库的标签
git push [origin(远程分支名)] [本地分支(可省略)] :[标签名] #2.删除(推送)至远程仓库
多人协作开发
多人协同开发,需要将用户添加进开发者,用户才有权限进行代码提交
情景一:单分支多人开发
分别有两个用户:用户一(windows系统)和用户二(Linux系统)上针对同一项目进行协作开发
目前远程仓库中,只有一个master分支,为保证主分支的稳定,在开发新功能时,建立dev分支(基于master分支最新状态)来开发
通过git pull
操作来将远程分支拉取到本地
注意: git pull
而且git branch
只能查看本地分支,要查看远程分支需要加上 [ - r ] 选项,前提是 pull 一下,拉取最新的远程仓库,才能看到最新的内容
git branch -r(或者git branch -a) #查看所有分支
gir branch -vv #查看本地分支和远程分支建立的连接关系
在本地仓库创建dev分支,并切换到该分支上,同时将本地dev分支和远程仓库origin分支下的dev相互连接起来:
git checkout -b [dev(本地分支名)] [origin/dev(远程仓库分支名)] #在本地仓库创建并切换到dev分支,同时将本地dev分支和远程仓库origin下的dev分支建立连接
git branch --set-upstream-to=[origin/dev(远程仓库分支名)] [本地仓库分支名] #远程仓库未和本地仓库某个分支建立连接时,可以使用该命令将分支进行连接操作
假如此时用户一(二)开发完毕进行了add、commit、push到了远程仓库,而另一个用户,比他晚一点提交,会出现分支冲突,此时需要拉取(pull)一下远程仓库(更新本地仓库到最新状态)然后再在本地解决冲突后,再去推送(add、commit、push)
记得,开发完毕后,合并分支的时候,先让master分支合并到dev上,出现冲突,在dev分支上解决完毕之后,再次把dev分支合并到master上面
同一分支进行多人协作的工作模式总结:
- 首先,试图用
git push [origin] [branch-name]
推送自己的修改 - 如果推送失败,则因为远程分支比本地的更新,需要先用git pull试图合并
- 如果合并有冲突,则解决冲突,并在本地提交
- 没有冲突或者解决掉冲突后,再用
git push [origin] [branch-name]
推送就可以成功 - 功能开发完毕,将分支merge进master分支,最后删除
情景二:多分支多人开发
一个需求或者一个功能点创建一个feature分支
两人分别开发两个功能:
A用户:
git checkout -b [feature-1] #新增本地分支 feature-1 并切换
vim [function1] #新增需求内容 - 创建function1文件
cat .\ [function1] #查看function1内容
……add、commit
git push origin [feature-1] #将feature-1推送到远端
远程仓库会自动创建新的feature-1分支
B用户与A用户一致
假如
B用户生病,需要A用户帮B用户继续开发剩下的代码,并且把feature-2分支名告诉了A用户,这时需要A用户在自己的机器上切换到featur-2分支继续开发
操作如下:
git pull #先拉取远程仓库内容
git checkout -b [feature-2] [origin/feature-2] #切换到featur-2分支并于远程仓库建立连接
……开发完成后推送至远端
之后B用户想要再自己的机器上看到最新的代码,需要拉取下最新的代码记录
注意:
merge之前最好切换到master分支下pull一下远端仓库,让master分支保持最新状态,然后再切换到feture分支合并master,最后push
远程分支删除后,本地git branch -a
依然能看到的解决方法
当远程仓库删除之后,在本地使用git branch -a
依然可以看到所有本地分支和远程分支时候时候使用:
git remote show origin #查看remote地址,远程分支还有本地分支相对应关系等信息
#根据提示使用:
git remote prune origin #删除本地关联的不存在的远程分支
企业级开发
系统开发环境:
- 开发环境:开发环境是程序员们专门用于日常开发的服务器
- 测试环境:开发环境到测试环境的过度环境
- 预发布环境:为避免因测试环境和线上环境的差异等带来的缺陷漏洞,而设立的,预发布环境服务器不在线上集成服务器范围之内,为单独的一些机器
- 生产环境:正式提供对外服务的线上环境
这几个环境也可以说是系统开发的三个重要阶段:开发 -> 测试 -> 上线,如图:
企业中环境不可能这么简单,这只是大致的简化
Git分支设计规范
不同的环境有不同的分支,例如:
分支 | 名称 | 适用环境 |
---|---|---|
master | 主分支 | 生成环境 |
relese | 预发布分支 | 预发布/测试环境 |
develop | 开发分支 | 开发环境 |
feature | 需求开发分支 | 本地 |
hotfix | 紧急修复分支 | 本地 |
注意: 以上表格中的分支和环境搭配仅是常用的一种,可视情况不同而定
master分支
master
是主分支,该分支为只读且唯一分支,用于部署到正式发布环境,一般由合并release
分支得到- 主分支作为稳定的唯一代码库,任何情况下不允许直接在
master
分支上修改代码 - 产品的功能全部实现后,最终在
master
分支对外发布,另外所有在master
分支的推送应该打上标签(tag)做记录,方便追溯 master
分支不可删除
release分支
release
为预发布分支,基于本次上线所有的feature
分支合并到develop
分支之后,基于develop
分支创建- 命名以
release/
开头,建议命名规则:release/version(版本)_publishtime(发布时间)
release
分支主要用于提交给测试人员进行功能测试,发布提测阶段,会以release
分支代码为基准进行提测- 如果在
release
分支测试出问题,需要回归验证develop
分支是否存在此问题 release
分支属于临时分支,产品上线后可选删除
develop分支
develop
为开发分支,基于master
分支创建的只读且唯一分支,始终保持最新完成以及bug修复后的代码- 根据需求大小程度确定是由
feature
分支合并,还是直接在上面开发(非常不建议)
feature分支
feature
分支通常为新功能或新特性开发分支,以develop
分支为基础创建的- 命名以
feature/
开头,命名建议:feature/user(用户名)_createtime(创建时间)_feature
- 新特性或新功能开发完成后,开发人员需合并到
develop
分支 - 一旦需求发布上线,便将其删除
hotfix分支
hotfix
分支为线上bug修复分支或补丁分支,主要用于对线上的版本进行bug修复,当线上出现紧急问题需要马上修复时,需要基于master
分支创建hotfix
分支- 命名以
hotfix/
开头,建议命名规则:hotfix/user(用户名)_createtime(创建时间)_hotfix
- 当问题修复完成后,需要合并到
master
分支和develop
分支并推送远程,一旦修复上线,便将其删除
测试,发布提测阶段,会以release
分支代码为基准进行提测
- 如果在
release
分支测试出问题,需要回归验证develop
分支是否存在此问题 release
分支属于临时分支,产品上线后可选删除
develop分支
develop
为开发分支,基于master
分支创建的只读且唯一分支,始终保持最新完成以及bug修复后的代码- 根据需求大小程度确定是由
feature
分支合并,还是直接在上面开发(非常不建议)
feature分支
feature
分支通常为新功能或新特性开发分支,以develop
分支为基础创建的- 命名以
feature/
开头,命名建议:feature/user(用户名)_createtime(创建时间)_feature
- 新特性或新功能开发完成后,开发人员需合并到
develop
分支 - 一旦需求发布上线,便将其删除
hotfix分支
hotfix
分支为线上bug修复分支或补丁分支,主要用于对线上的版本进行bug修复,当线上出现紧急问题需要马上修复时,需要基于master
分支创建hotfix
分支- 命名以
hotfix/
开头,建议命名规则:hotfix/user(用户名)_createtime(创建时间)_hotfix
- 当问题修复完成后,需要合并到
master
分支和develop
分支并推送远程,一旦修复上线,便将其删除