4 Git的基本原理
通过上面的内容大家应该熟悉Git的基本使用了,但是我们不仅仅要会敲几个命令,当然这是生产力需求,我希望大家还能知道其实现原理,这样可能更加有利于对Git的理解。
Git在版本控制领域为什么能脱颖而出,优势是啥?
- 可以为每一次的变更提交版本更新并备注更新内容
- 自如的创建合并分支
- 多人协作开发
- 清晰的对比不同版本的差异
- 历史版本的自如切换
从上面的几条特点,总结出来就是对文件变更的存储,我相信大家如果把这一点铭记于心,一定会在学习Git的路上事半功倍。
其实我们每个人都应该有备份数据/文档的经验,不然硬盘坏了怎么搞,去修?真的很贵,而且不一定可以恢复。我们平时备份的常规手段,无外乎就是另存为,然后修改名称避免命名冲突,其实在Git中采用的类似的思想。当我们提交一份代码或者文档给Git系统的时候,它会保存整个版本,这和另存为是不是有异曲同工之妙,不过呀,采用的存储方式就不一样了,它使用了"Git数据库"。
Git数据库
要了解Git数据库,我们需要掌握Git的三个命令就好了
- git init
用户创建git空仓库或者重置一个存在的git仓库
- git hash-object
这个命令可能很多小伙伴少见,这是git的底层命令,用来想git数据库写入数据
- git cat-file
下面开始实操,大家可以一起来,这样可以加深影响
- git init Test
此时会在当前目录生成一个git仓库,因为是一个隐藏文件,所以我们需要ls -al查看。
- 查看git仓库结构,执行如下命令
圈出的objects目录为git数据库的存储位置
- git数据库的写入操作,下面向git数据库写入一些内容
解释下这几句命令中的一些注意事项。"|"管道符,“|”前面的内容的输出作为后面内容的而输入。-w -stdin意思是向git数据库写入一条数据,这条数据的内容从标准输入中读取
下面再说说输出结果,当命令执行完后,会返回一个40位的hash值,这个hash+头部的校验运算从而获取校验和。git数据库其实算一个键值数据库,当我们向数据库插入任意数后都会返回一个健值,通过所返回的键值再次检索内容。
通过find查看objects目录发现多出一个文件,文末命名采取hash值的前两位作为目录名,其中的文件为hash的后38位。总之git数据库的存储方式为一个文件一条内容
- Git数据库的查询
通过cat-file这个命令查看数据库中某个键值对应的数据,使用-t选项查询键值对应的数据类型,使用-p选项查看数据的内容。从结果可得出,所查询的数据类型为blob,内容为version v1,现在我们已经实现了如何向Git数据库中插入数据。这么简单?是的,这就是它的核心,在git数据库中存储纳入git版本管理的所有文件的所有版本的完整镜像。
- Git文件跟踪
所谓文件跟踪无外乎是我们需要将文件或者文档的变更状态给记录下来。下面我们通过git底层命令模拟一次变更的过程
echo "version v1" > test2.txt
git hash-object -w test2.txt
如果你足够细心,你会发现返回的hash值和上面是一样,因为我们写入的内容一致,git hash-object命令在只指定-w选项的时候,会把file.txt文件内容写入数据库。继续执行
find .git/objects -type f
此时你会发现还是只有刚才的那个文件。从此得出git数据库在存储文件的时候,只关心文件的内容,不关心文件的名字。
此时我们修改文件内容
echo "version xiaolanv2" > test2.txt
git hash-object -w test2.txt
find .git/objects -type f
这个时候发现多出了一个文件。因为我们写入了新的数据到数据库,接下来我们使用git cat-file查看两条数据分别是什么
得出结论:我们的变更被完全记录下来了,如果此时我们需要将test2.txt中文件恢复到之前的状态,怎么操作?
cat test2.txt
git cat-file -p f914c > test2.txt
从上面结果我们得出:test2.txt成功恢复到修改之前的状态"version v1",这就是版本回滚的实质。
我们继续,看看其他追踪记录如何做的
- 如何利用树的对象解决文件保存和文件组织的问题
这个熟悉,数据结构中的树,它可以方便的将多个文件组织起来。Git通过树对象将数据对象组织起来。一棵树包含多条记录,其中每个记录是一个blob对象或者tree对象的指针。ok,这样的话,我们就可以将文件系统各个时间节点的状态保存在git数据库中,就问你激动不激动,不管你的项目多么复杂,不就是文件/目录?交给树对象完事,那么如何创建树对象?
首先要使用树对象,还是先熟悉几个命令
git update-index //创建暂存区
git ls-files --stage 查看暂存区内容
git write-tree 将暂存对象写入到树对象
好勒,我们把test2.txt内容存放在暂存区
find .git/index //发现不存在index
git update-index -add test2.txt
find .git/index
cat .git/index
find .git/objects/ -type f
git ls-files --stage
git write-tree
git .git/objects/ -type f
这里尤其注意观察index的变化,我们在添加test2.txt到暂存区前,index文件根本不存在,当添加test2.txt到暂存区后就发现index文件被创建了。
另外,我们看看git数据库是怎么变化的,当我们执行完update-index后,git数据库实际上没有变化,只有执行git write-tree后,git数据库中多出了一条记录是一个tree对象,其hash值如图
从结果可知,git数据库新增了一个tree对象,此tree对象指向一个blob对象,这个blob对象就是我们之前添加的数据的内容。
上面我们尝试了,使用已经存在缓存区的数据进行实验,如果我们新建一个文件保存到缓存区,随后保存为tree对象,又会出现怎样的结果?
echo "xiaolan" > lan.txt
git update-index --add lan.txt
find .git/objects/ -type f
git write-tree
find .git/objects/ -type f
git ls-files --stage
这一次我们可以发现,当执行update-index后,git数据库就发生了变化,并增加了一套hash值为""的数据;另外,暂存区也多了文件lan.txt的相关信息,说明了什么?
- 如果添加暂没存储的数据到暂存区,当执行update-index后会同时保存到git数据库
- 添加文件到暂存区是一个add追加操作,之前已经在暂存区的数据并不会清空,一直存在
查看新添加文件的tree对象
git cat-file -p
这一次写入数据库的tree对象存在两个文件
我们大胆想一想,如果是一个目录或者子文件夹能够保存为对象?ok,尝试一波
mkdir lan
git update-index --add lan
给我们报错了,暂时说明不能把空的文件夹保存为tree对象,那我们造一个文件
echo "lan lala" > lan/lan.txt
git update-index --add lan/lan.txt
git ls-files --stage
git write-tree
git cat-file -p bde7e6
ok,从结果我们得出文件夹对应一个tree对象。但是我们之前不是说git可以管理我们的变更记录吗。我们为了保存文档的不同版本采取时间的关系,在git中也是采用了时序关系,下面我们继续看看
- 利用提交提交对象记录版本之间的版本时序关系和版本注释
咋们使用conmit记录什么时间,什么人,因为什么原因提交了新的版本,那么这个版本的爸爸是谁?
git提供了底层命令commit-tree来创建对象,但是我们需要给这个命令提供树对象以及hash键值以及这个对象的父提交对象
git write-tree
git commit-tree cbofbcc -m "firest commit"
git log a18effce
从上面的结果,我们可以得出提交一个对象实际上包含了所提交的树对象hash键值以及author和commiter,以及修改和提交的时间,最后是提交的注释
其中author和committer我们可以通过git config设置
下面我们修改某个文件然后重新创建一个树对象作为此项目的第二个提交版本
echo "xiaolan chongchong" > test2.txt
git update-index test2.txt
git write-tree
使用git log查看最近一个提交对象的提交记录
牛皮,至此我们模拟的git的提交过程,说这么多,平时我们根本是用不了这些命令,但是当我们遇到棘手的问题的时候是不是可以更清楚哪里出问题,更直接一点,当面试的时候,让你说说git某一块的底层原理,你是不是可以掰扯一句(ps,当初我面试PDD的时候,什么gdb的底层实现,git底层逻辑都会被照顾),所以我认为对于相关技能的掌握重要,对于其技术的底层原理还是需要有所理解。至此,总结下
Git通过数据对象,树对象以及提交对象(最终以/.git/objects响应目录呈现)完成真个提交过程。其中将修改的文件保存为数据对象,更新缓存区,记录树对象最后创建指明父对象的方式完成提交。
难道这样就完事了?每次都要去读苦涩难懂的一串hash值,就反复我们不喜欢IP更喜欢域名一样,提供了"引用"技术
- 引用
这里的引用类似与指针,存放在.git/ref中,它指向一个hash键值。那么如何创建?
我们只需要将hash键值保存到以引用名字的文件即可
$ echo "21715108d32ede654804400273b23926a9411974" > master
$ cat .git/refs/heads/master
21715108d32ede654804400273b23926a9411974
git log master
这样我们就指向了一个最新提交的引用叫做Master
所以我们之前使用git log 2171510现在使用git log master即可
是不是和上面一模一样。
小结下:
Git的核心是它的对象数据库,其中保存着git的对象,其中最重要的是blob、tree和commit对象,blob对象实现了对文件内容的记录,tree对象实现了对文件名、文件目录结构的记录,commit对象实现了对版本提交时间、版本作者、版本序列、版本说明等附加信息的记录。这三类对象,完美实现了git的基础功能:对版本状态的记录。
****
Git引用是指向git对象hash键值的类似指针的文件。通过Git引用,我们可以更加方便的定位到某一版本的提交。Git分支、tags等功能都是基于Git引用实现的。
https://www.cnblogs.com/mamingqian/p/9711975.html
- https://segmentfault.com/a/1190000022338943
5 Git和Github
什么是Github
Github是为开发者提供Git仓库的托管服务,是一个让开发者和朋友同事共享代码的场所。
Git和Github的简单区别
在Git中,开发这将代码存入命名为Git仓库的资料库中。而Github是给Git仓库提供服务
Github提供了那些功能
- 协作方式的改变
- 开发者之间引发化学反应的pull request
通过pull request,开发者在本地修改代码后向Github中托管的Git仓库请求合并。对于其他开发者而言,可以查看并修改这些代码,或者说申请修改代码并发送请求,如果请求合理可进行代码的合并。另外还可以对开源的每行代码进行测试讨论。如果想了解某个开源的项目的动态信息,第一时间掌握信息,就可以将项目放入watch中
Github提供的主要功能
21
- Git仓库
可以免费建立自己的Git仓库,如果想针对个人等情况的私人仓库则需要付费
- organization
个人免费申请即可,如果是企业则会付费
- issue
类似于bug追踪系统。每一个功能更改或修正都对应一个 Issue,讨论或修正都以这个 Issue 为中心进行。通过这个issue,我们可以了解到更改的一切信息,方便我们进行统一的管理
- wiki
Wiki,任何人都可以对一篇文章进行更改和保存,所以可以多人共同完成一篇文章。用什么语法写呢,采用gfm语法编写
- pull request
当我们想对其他开源项目提交自己代码时,可以通过pull request申请,请求开发人员合并。同时也提供了代码比较功能
6 Git导入
我们知道linux内核开发工程浩大,大哥torvalds每次发生代码的变更后都很麻烦,然后决定开发一个版本管理系统,也就是Git
- 什么是版本管理
版本管理是管理更新的历史记录。在开发的过程中,如果不小心误删代码可以通过Git回滚到之前的版本
- 集中性与分散性
如下图所示。集中型是将所有的数据存放在服务器当中,比较方便管理。但是如果中央服务器出现单点故障,假设宕机一小时,此时谁也无法提交更新也就无法协同是工作。另外如果中心数据库所在的磁盘发生了损坏,毫无疑问损失所有数据,变更历史自然也没有了,所以出现了分布式版本控制系统。
分散性
分散性体现在Github可以将仓库fork给每一个用户。意味每个开发人员可以将仓库中的内容复制到自己的账户上。作为客户端,不仅仅只是提取最新版本的快照,而是吧代码仓库整个镜像拉取下来,这样即使某个服务器发生故障,可以使用其他的镜像进行恢复。
三大重要概念
- 已提交
表示数据已经安全的保存在本地的数据库
- 已修改
表示修改了文件但是还没有保存到数据库
- 已暂存
表示对修改的文件进行了标记
7 Github详解
- 创建账户
- 设置ssh key
- 添加公开密钥
- 使用社区功能
实际操作
- 创建仓库 repository name
- Description 设置仓库的说明
- initialize this repository with a readme 打勾则自动创建readme
- add a license 表明改仓库的许可协议
clone公开代码
- Git clone Git@Github.com/hello
- cd hello
- Touch a.c
- Git status
- Git add a.c
- Git log 查看提交日志
- Git push 此时查看Github已更新
Github的界面
工具栏
- 搜索窗口
通过代码片段或者用户可以搜索到与之相关的信息
- Explore
各个角度介绍目前Github中热门的软件,其中按照筛选本日本周本月的热门仓库
- Create a new。。。
创建Git仓库,想organization添加承欢,添加Issue等都在这
控制面板
- pull requests
显示用户已进行过的 Pull Request。通过这里,开发者可以很方便地
追踪 Pull Request 的后续情况
- issues
查看拥有权限的仓库或者分配给自己的issue
- stars
按照列表的方式显示用户添加到star的仓库,当有些仓库需要经常查找但是又不希望经常出现的watch中,就可以放在star中
- repositories you contribute to
显示咋们做过贡献的仓库
- your repositories
按照更新时间显示用户的仓库
个人信息
- popular repositories
公开仓库中受欢迎且比较多star的项目
- Public contributions
一格表示一天,记录当日用户对拥有读取权限的仓库的大致贡献 度
- Public activity
显示该用户的公开活动(图 5.6)。活动就是指这个用户做了什么, 比如向仓库进行提交或者 Pull Request 等,其大量的公开信息都会记录 在这里
仓库
- watch/star/fork
watch表示如果此项目更新就可以显示给用户公共活动中。star越高,受关注越多。fork越多表示参与这个仓库开发的越多
- issue
用于 BUG 报告、功能添加、方向性讨论等,将这些以 Issue 形式进 行管理。Pull Request 时也会创建 Issue。旁边显示的数字是当前处于 Open 状态的 Issue 数
- Graphs
图表形式显示该仓库的各项指标
- ssh clone url
clone 仓库时所需的 URL。点击右侧的剪贴板图标可以将 URL 复制 到剪贴板中。点击 HTTPS、SSH、Subversion 图标可以切换至相应协议 的 URL。
- commits
查看当前分支的提交历史。左侧的数字表示提交数
- releases
仓库标签列表
- contributors
显示对该仓库进行过提交的程序员名单
- branch
显示当前分支的名称
9 实际操作学习Git
- Git init
使用版本控制之前先使用Git init进行仓库的初始化,此时会生成.Git目录,此目录管理当前目录所需的仓库数据
- Git status
从上图可以知道此时的分支是在master上,并显示目前没有可以提交的内容,所以我们可以创建一个readme.md,然后使用Git status发现已经发生变化
- Git add 向暂存区添加文件
为了让文件成为Git的仓库管理对象,需要先放入暂存区。相当于提交之前的暂存区域
- Git commit
通过此命令将暂存区的内容保存到仓库的历史记录中,同时养成好的习惯,使用-m参数养成好的习惯,每次提交加上注释。不过,如果需要查看详细的信息,直接Git commit
- Git log
查看以往仓库中提交的日志,其中包括什么时候提交,什么时候合并了
- Git log --pretty=short
查看提交信息的第一行
- Git log -p
查看提交带来的改动
- Git diff
查看工作树和暂存区的差别,这里注意“+”代表新增的行,“-”代表删除的行
- Git diff head查看工作树和最新提交的差别,因为head指向当前最新提交的指针
分支操作
一个项目通常为多个开发人员一起合作,每个开发人员都会在本地fork一份代码,现在有两个分支,主分支为master
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0AgrUbMc-1597210565594)(/Users/qianxiao/Library/Application Support/typora-user-images/image-20200811150255586.png)]
- Git branch 显示分支一览表
*号代表当前的分支
- Git checkout -b。创建 切换分支
Git checkout -b 以当前master分支为基础创建新的分支
- 切换到a分支并进行提交
Git checkout -b A. 等价为 Git branch A Git checkout A
- 再次查看分支列表
- Git add readme.md
- Git commit -m "add a"
- 切换到master分支 Git checkout master
- 切换到上一个分支 Git checkout -
Git merge合并分支
- 假设目前A分支已经完毕,此时执行Git merge --no-ff A
- 图形方式查看分支 Git log --graph
- Git log --graph图表方式查看提交日志
回溯版本
- Git reset 回溯历史版本
冲突解决
- 打开编辑器
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qzkOVb6F-1597210565595)(/Users/qianxiao/Library/Application Support/typora-user-images/image-20200811153534780.png)]
两者的内容从======分开,通常需要删除其中之一然后add commit[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ibpKSBuH-1597210565595)(/Users/qianxiao/Library/Application Support/typora-user-images/image-20200811153749159.png)]
- Git commit --amend 修改提交信息
推送到远程仓库
- 添加远程仓库 Git remote add
- 推送到master分支 Git push -u organ master
表示将 oriGin 仓库的 master 分 支设置为本地仓库当前分支的 upstream(上游)
- Git push -u oriGin d 前提是先创建分支d
从远程仓库获取
- Git clone Git@xxx
pull request
修改其他开源项目代码后,请求合并的一种行为
流程
- Github上发送request
- 接收方评估并给予反馈,同意则成为贡献者
发送pull request前的准备
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JutJCA0R-1597210565596)(/Users/qianxiao/Library/Application Support/typora-user-images/image-20200811160234653.png)]
- 查看修正源代码
- fork项目---创建自己的项目,新建为自己的账户吗/first-pr
- Git clone到本地开发环境
- 养成先生成分支再修改代码的习惯
- Git clone -a 查看当前分支
- 创建一个work分支 Git checkout -b work gh-pages
- 确认是否切换到分支 Git branch -a
- 创建index.html
- 提交前修改 Git diff
- 创建本地work分支的对应远程分支 Git push oriGin work
- 查看分支 Git branch -a
- 查看GitHub中是否添加了我们分支
- 发送pull reques 确认无误后 点击create pull request,并填写提交的理由
- 点击send pull request 这样GitHub 会帮我创建pull request和issue
让 pull request更加有效的方法
-
仓库的维护
保证仓库的最新
- fork或者clone仓库 Git clone
- 给原仓库设置名称 Git remote add upstream Git://
- 获取最新数据 Git fetch upstream,将upstream分支与当前分支合并
接受pull request
10 与GitHub相互协作的工具
11 使用Github的开发流程
然而在公司企业的开发中,开发者每天都要见面,要经常互相发送 Pull Request,这种流程就显得有些繁琐了。因此,下面我们要介绍一个 不需要 Fork 仓库的工作流程。这种方法可以让每一名开发者都掌握着 一个本地仓库和一个远程仓库,使整个开发流程变得简单
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tobEzcVI-1597210565597)(/Users/qianxiao/Library/Application Support/typora-user-images/image-20200811162522772.png)]
客户端推荐
Github for mac
Github for windows
提供的功能
- 从Github中clone仓库
- 显示仓库历史记录
- 提交仓库的修改内容
- 切换分支
- 想Github端进行push
sourcetree
除了上面的功能还支持Git-flow
12 Git+jenkins
当我们就自己一个人开发代码的时候还行,很自由。当多个开发多个人员一起开发的时候,如果同时修改一份代码,很容易导致冲突。后面开发人员越来越多,代码文件也越来越多,每天准备下班前合并代码全是失败,不得不加班,怎么搞?
- 合并代码的周期缩短,随时随地合并,这叫做持续集成。那持续集成具体是什么?
持续集成是频繁的将代码集成到主干,主要的优点
- 快速发现错误。完成点更新就提交到主干,发现错误也比较容易
- 防止主干大幅度的偏离主干,否则分支越来越复杂,后续的集成越来越困难
对于运维有什么改变?
- 在过去,对于初级运维,开发每天合并一次代码,运维pull下来测试
- 随着持续集成的引进,不停的提交代码,运维感觉好苦逼,不停测试
- 回到家里,睡觉都在思考有没有自动化一点的方案
- 好,出现了jenkins
- 每档代码上次到Gitlab,Gitlab会通知jenkins,然后jenkins就会打包发个测试服务器进行测试,并将测试结果给运维
- 运维聪明的很啊,干脆把jenkins都给开发,自己就可以看结果就行
- 这样的自动化测试呢就叫做“持续交付”,那我们说说持续交付
持续交付是生产阶段的前一个阶段,交付给用户和质量团队进行评审,那么对于运维上线又有什么改变呢
- 代码测试,评审通过,准备上线,运维开始干活
- 拿来自动化部署工具一梭子买卖,分分钟完事,比如ANSIBLE,这叫做持续部署
说了这么多,我们该看看这个jenkins这个生产工具了