参考文章:
Git基本知识
1.Git定义
git的本质就是要实现对文件变更过程的存储
Git是一种分布式版本控制系统:
- 版本控制:git可以对文件变更过程进行管理,随时可以拿出所需要的版本
- 分布式:Git的数据不仅存在远程仓库(即Git平台的服务器),也存在本地仓库(即项目开发者的本地计算机),所以称为分布式
2.Git功能列表
- 我们可以为每一次变更提交版本更新并且备注更新的内容
- 我们可以在项目的各个历史版本之间自如切换
- 我们可以一目了然的比较出两个版本之间的差异
- 我们可以从当前的修改中撤销一些操作
- 我们可以自如的创建分支、合并分支
- 我们可以和多人协作开发
- 我们可以采取自由多样的开发模式
3.Git基本术语
工作区:在编辑器内可增加或修改文件等位置
缓存区:提交代码,解决冲突的中转站
版本库:本地
.git/
目录,内含缓存区本地仓库:连接本地代码和远程代码的枢纽,在没有联网状态下,本地代码可先提交至该处
远程仓库:托管远程代码的中央服务器
4.Git托管平台
(1)Github
Gihub是全球最流行的代码托管平台,拥有大量非常知名的开源项目。github的服务器部署在国外,国内访问不友好,适合用于学习和开发开源项目
(2)Gitlab
Gitlab支持免费搭建私有库,功能强大,对企业级的内部开发友好,较为笨重不适合用于简单项目的搭建
(3)Gitee
俗称码云,国内知名的在线代码托管平台,基于gitlab
(4)Coding
Coding 是一个面向开发者的云端开发平台,提供git/svn 代码托管,并且面向个人提供私有库。
对国内小团队开发较为友好,个人比较推荐,下面的一些操作都会基于Coding平台
Git快速入门
1.Git安装
git官网地址:https://git-scm.com/
2.Git密钥
本地仓库与远程仓库通过SSH公私钥进行身份确认,
(1)生成公钥
终端输入命令生成公钥
1 | ssh-keygen -o |
连续三次回车使用默认设置
(2)查看公钥
旁边的id_rsa存的就是私钥
1 | cat 【公钥保存位置】/id_rsa.pub |
(3)上传公钥
打开coding的个人设置页面即可上传公钥
(4)Git连接测试
通过以下命令即可测试ssh连接是否成功(第一次连接需要一个输入yes的步骤)
1 | ssh -T git@e.coding.net |
3.Git项目创建实战
(1)Coding上创建项目
点击创建项目
选择模板2中的代码托管即可
填写项目基本信息,即可完成创建
(2)创建代码仓库
在项目下创建代码仓库
基本设置如下:
(3)将项目克隆到本地
复制仓库的ssh链接,将一个仓库内的代码使用ssh下载到本地
电脑上执行git clone命令,将远程仓库克隆为本地仓库
1 | git clone 【SSH链接】 |
有时会出现下载时权限不足的情况,但是ssh的连接测试又能成功,这时候可以尝试一下重新提交公钥
进入项目文件夹
在git bash下可以看到Test文件有master的符号
(4)设置签名
设置签名以区分不同开发者的身份(与代码托管中心的账号,密码无关)
1 | git config user.name "Autovy" |
设置完成后,即可在配置文件看到相关信息
1 | cat ./.git/config |
(5)代码提交流程
修改项目文件
创建一个html文件
1 | echo "test" > index.html |
修改md文件
将工作区改动内容提交到缓存区
1 | git add ./ |
查看工作区和缓存区状态
可以看到文件改动情况
1 | git status |
将缓存区的内容提交到本地仓库
1 | git commit -m "v1.1:添加index文件" |
查看历史记录
1 | git log |
将本地仓库的内容推送到远程仓库
1 | git push |
最后可以看到Coding仓库变化
Git高级使用
1.Git版本管理
(1)更新1.2为最新版
重复上面步骤,修改并更新index.html文件
1 | vim index.html |
修改md中的版本信息
1 | vim README.md |
将1.2版本的代码按步骤提交到本地仓库
1 | git add ./ |
这里先不推送远端仓库,方便后续切换版本
(2)查看日志
git log 查看提交日志
1 | git log |
commit后面一大段的字符串是commit id(版本号),这里HEAD
是Git指向当前版本的指针,所以要切换版本就要移动HEAD
到指定版本
(3)修改未添加缓冲区
修改index.html文件
1 | vim index.html |
在对文件使用add ./
将项目添加缓存区之前,我反悔了,决定丢弃工作区的修改
查看当前状态,可以看到提示使用git restore
丢弃修改
1 | git status |
丢弃修改工作区的修改,文件恢复到最近一次git commit
或git add
时的状态
1 | git restore ./ |
(4)修改添加到缓存区
修改index.html文件
1 | vim index.html |
添加修改到缓冲区
1 | add ./ |
查看当前状态,可以看到提示使用git restore --staged
丢弃缓存内容
1 | git status |
丢弃缓存内容后,再按上一种情况的流程恢复工作区
1 | git restore --staged ./ |
(5)修改提交到本地仓库
将版本回退到上一个版本即1.1
1 | git reset --hard HEAD^ |
这时本地仓库就回退到了1.1版本
这时候查看git log,可以发现1.2的提交记录就移除
如果又需要回到1.2版本
先通过git reflog
查看历史命令,可以找到1.2版本的id
1 | git reflog |
同样可以使用git reset切换版本
1 | git reset --hard 【commit id】 |
这里的commit id不用填完整,填到后面的字母部分
这时本地仓库又切回了1.2
【注意】
如果已经将最新的代码推送过远端仓库(即远端仓库有最新的版本,要保持本地和远端的资源一致),会报以下错误、
(6)修改推送到远程仓库
1 | git revert HEAD --edit //并修改提交信息 |
这时后查看记录git log,可以看到撤销的操作也被记录其中
这时本地仓库就是回退到了版本1.1
直接推送到远程仓库,远程仓库也回退到1.1版本
如果提交了1.1版本后,又需要回到1.2版本,这时候只要撤销掉对1.2的撤销即可
再git push推送到远程仓库即可
(7)总结
- 工作区修改后:
git restore ./
恢复工作区 git add ./
添加到缓冲区后:git restore --staged ./
恢复缓存区git commit
提交到本地仓库后:git reset --hard HEAD^
恢复上个版本到缓存区git push
推送到远程仓库后:git revert HEAD --edit
撤销上一个推送操作,并恢复到缓存区
2.Git分支管理
(1)分支解决的问题
同时并行推进多个功能开发,提高开发效率
各个分支在开发过程中,如果某一个分支开发失败,不会对其他分支有任何影响。失败的分支删除重新开始即可
最后再将分支合并到master上
分支:
分支的合并(默认快速模式):
分支的合并(普通模式):
(2)分支基本操作
创建dev分支
1 | git branch dev |
git checkout -b dev
可以创建并切换到dev分支
查看当前分支
1 | git branch |
可以看到dev分支已经创建成功了,而当前位于master分支
切换到dev分支
1 | git switch dev |
修改文件内容
1 | vim README.md |
将最新修改的内容提交到dev分支
1 | git add ./ |
切换回master分支,并查看文件内容
1 | git switch master |
可以看到主分支上没有dev的内容
合并分支
在master分支上合并dev
1 | git merge dev |
可以看到在dev上的修改在master上生效了
(3)分支合并冲突问题
前面的情况中,dev是在master基础上的,合并前master并没有修改,如果master再合并前与dev修改了同一行的内容就会出现冲突
先撤销一下合并
查看命令记录,可以看到合并记录中合并的方式是快速模式(即直接把master
指向dev
的当前提交,所以合并速度非常快)
1 | git reflog |
撤销合并(未推送)
1 | git reset --hard HEAD^ |
修改master内容,并提交到本地仓库
1 | vim README.md |
1 | git add ./ |
再次合并dev分支
1 | git merge dev |
这时就会出现合并冲突的问题
解决冲突
编辑README.md文件
1 | vim README.md |
删除特殊符号,修改内容
然后正常提交到master分支即可
1 | git add ./ |
一般来说不会再本地仓库库对分支进行合并,而是在远程的代码平台进行分支的合并
推送内容到master
1 | git push |
由于远程仓库还没有创建dev分支,所以推送时要创建分支
1 | git push --set-upstream origin dev |
这时即可在Coding上看到两个分支了
代码平台上也可以合并分支,当如果有冲突就无法合并
(3)分支中的多人协作
准备wsl模拟另一台电脑,一个新的coding账号,新的coding账号提交wsl中的ssh,然后使用两个浏览器登录分别登录两个账号
将第二个账号(二号机)邀请到主账号的团队中
将二号机拉入项目中
二号机wsl看到项目后克隆项目到本地
1 | git clone git@e.coding.net:autovys/demo/Test.git |
进入目录后查看分支,可以发现只能看到master分支
1 | git branch |
二号机要在dev基础上进行开发,就要创建远程origin
的dev
分支到本地
1 | git checkout -b dev origin/dev |
二号机在dev基础上修改了文件内容
1 | vim README.md |
并且二号机还推送到了远程仓库
1 | git add ./ |
这时一号机也要对同样的文件修改一些内容,并尝试推送到dev分支
1 | git switch dev |
1 | git add ./ |
可以发现远程推送失败了,因为二号机的最新提交和我推送的提交有冲突
这时就需要先git pull
把最新的提交从origin/dev
抓下来,然后,在本地合并,解决冲突,再推送
1 | git pull |
这时候解决的方法和分支管理中的解决冲突完全一样
编辑README.md文件
1 | vim README.md |
删除特殊符号,修改内容
然后正常提交到dev分支即可
1 | git add ./ |
IDEA中的Git
1.基本代码提交流程
(1)创建空项目
在Coding上创建新的空的代码仓库
idea克隆项目到本地
填写远程仓库的SSH地址和下载的本地位置完成下载
(2)IDEA打开项目文件
IDEA打开项目文件后,可以看到IDEA自动识别了git,出现了以下的一些工具
(3)代码提交流程
修改README.md文件如下,即在工作区改动了代码
将代码添加到缓冲区(git add ./),这一步可以省略,直接提交idea会帮你做这一步操作
提交代码到本地仓库,注意填写提交信息(git commit -m "message"
)
最后推送到远程仓库,即完成一次代码提交(git push
)
关于提交的一些信息我们都可以在git工具栏的控制台进行查看,可以看到git工具的控制台帮我们输入的命令内容
2.Git版本管理
(1)更新版本到1.2
(2)查看提交日志
可以看到idea使用git不需要设置签名,而是默认使用了远程仓库平台的注册信息
(3)修改未添加缓冲区
代码未添加到缓冲区,需要恢复工作区的内容(简单呀,疯狂ctrl + Z 😎)
可以使用回滚,即可恢复到最近一次git commit
或git add
时的状态
(4)修改添加到缓存区
版本1.2已经添加到了缓存区,同样可以通过上面的方法恢复工作区
(5)修改提交到本地仓库
这时候可以看到回滚的按钮变灰了,这时我们可以打开git工具区对master分支进行操作,idea提供了三种还原的方法,都可以让我们恢复1.1版本
还原提交,是多加一步撤销的操作,恢复内容到本地仓库,可以直接提交
撤销提交,是让1.2版本恢复到缓存区,这时候可以通过回滚恢复到1.1版本
删除提交,直接恢复1.1版本到工作区
所以推荐使用还原提交,如果要丢弃的版本就使用删除提交,否则idea无法恢复已撤销或删除的提交(命令行可以恢复)
(6)修改推送到远程仓库
推送到远程仓库后,我们可以看到我们只剩下还原提交可以使用了,效果和上面的一样
idea还提供了将当前分支重置到此处的功能,其相当于进行多次的删除提交操作。在修改已提交到本地仓库的情况下,可以开始回溯到选择的版本;在修改已推送到远程的情况下可能会造成冲突问题,所以该功能慎重使用
)
(7)还原提交的问题
还原提交也可以还原更远的版本,但是会出现冲突问题,
接收他们的则回滚到对应版本,合并则解决两个修改的冲突
合并的页面如下,点击箭头即可自动修改冲突点
3.Git分支管理
(1)分支基本操作
创建分支dev
git工作区右键选择分支签出(checkout,什么铸币翻译)即可切换分支,注意记得先切换好分支再对提交的路径图进行操作,否则它提交的到的是当前的分支,即便是在另一条分支上做的操作
这里在dev上操作的还原提交会被提交给master,所以要记得切换分支(这算不算一个小bug呢?),另外要删除一个分支时也要先切换到其他分支
修改文件提交到dev
直接选择推送,远程仓库就可以发现dev分支被创建好了
合并master和dev分支,当前选择的是master分支,所以选择将当前合并到已选择(渣翻ks8)即为将dev合并到master
(2)分支合并冲突问题
先删除合并的提交恢复master,修改master的文件并提交
这时候合并就会出现合并冲突现象
前面两个是对master修改的内容与dev修改的内容做一个选择,选择合并则需要手动修改解决冲突
点击箭头即可合并修改
(3)分支中的多人协作
推送上面修改的master分支和dev分支,然后删除本地项目文件(即删除了本地仓库),进行一次remake
重新使用idea克隆项目到本地,可以看到本地只拉取了master分支
这时候就需要右键点击远程的分支,新建dev分支到本地
在Coding平台上改动dev分支的文件,模拟另一个开发人员在协同开发的情况下修改了文件
这时候我们在本地也修改了dev分支文件并推送
这时候idea会有弹窗提醒推送被拒绝,并在控制台提醒你用git pull
idea是有git pull 这个按钮的
但是点击更新到dev时,会提示你本地的dev没有追踪远程的分支
点击提示选择上游分支同步远程的dev分支即可
如果其他开发者新的更新和我更新的有冲突,这时候就是我们熟悉的冲突页面啦
解决冲突后我们就可以推送最新版到远程仓库了,这样的机制保证了git协同开发的可能性
Git原理
Git本质是一个内容寻址的文件系统,其次才是一个版本控制系统
每次我们运行 git add
和 git commit
命令时, Git 所做的实质工作是将被改写的文件保存为数据对象,更新暂存区,记录树对象,最后创建一个指明了顶层树对象和父提交的提交对象
Git的核心是它的对象数据库(.git/objects文件),其中保存着git的对象:
- blob对象:实现了对文件内容的记录
- tree对象:实现了对文件名、文件目录结构的记录
- commit对象:实现了对版本提交时间、版本作者、版本序列、版本说明等附加信息的记录
1.Git数据库
(1)Git文件结构
新建文件夹并将其初始化为一个空的git仓库
1 | mkdir Test |
进入git文件使用以下命令查看git仓库的文件结构
1 | cd Test/.git |
- config是git基本配置文件
- description是GitWeb专用的文件
- info文件夹是全局性排除文件(与.gitignore互补)
- hooks存放钩子脚本
- HEAD记录当前checkout的分支
- refs提交对象的指针
- objects存放所有数据,这就是我们要找的数据库了
(2)Git数据库写入操作
Git会根据文件内容计算出一个hash值,以hash值作为文件索引存储在Git文件系统中
git hash-object
可以用来计算文件内容的hash值,并将生成的数据对象存储到Git文件系统中
1 | echo "version 1" | git hash-object -w --stdin |
-w
表示将数据对象写入到Git文件系统中,--stdin
表示从标准输入中获取文件内容
命令执行完成后返回一个哈希值,它就是git数据库中的键值(key),通过键值我们可以再次检索到插入数据库的内容(value),实际上git数据库就是一个简单的键值对数据库
查看object文件,可以看到新生成的文件,这个文件存储以新存入数据对应hash值前2位命名的文件夹内
(3)Git数据库查询操作
既然我们拿到了键值,那肯定可以查询到其对应的内容,复制hash值进行查询
1 | git cat-file -t d73e3875a8db6b402e2ce905c4a2222603c1f090 |
-p
表示查看Git对象的内容,-t
表示查看Git对象的类型
blob对象是git数据库中的数据对象,此外数据库中还有树对象(tree)和提交对象(commit)
4.使用Git跟踪文件变更
接下来我们将模拟一次文件变更过程,看看git的对象数据库能不能实现“跟踪文件变更”的功能
(1)创建文件
创建一个txt文件写入内容“v1.1”,并写入到git数据库中
1 | echo "version 1" > file.txt |
(2)修改文件
修改file.txt的内容,并再次提交到git数据库
1 | echo "version 2" > file.txt |
可以看到这次的hash值已经发生改变了,而objetcs内会多出一个文件
查询它们对应的内容如下
说明我们对同一个文件修改后的内容也会经过git数据库的哈希处理再数据库生成新的数据对象,以此记录文件的不同版本
(3)恢复文件
所以我们想要把文件恢复到文件修改前版本,只需要在数据库取回即可,这就是git版本回滚的实质
1 | cat file.txt |
数据对象只是解决了文件内容存储的问题,而文件名的存储则需要通过树对象来解决
5.Git的树对象
Git通过树对象(tree)将数据(blob)对象组织起来——其类型于一种文件系统:blob对应文件内容,tree对象对应的目录和节点
有了数对象,我们就可以将文件系统任何时间点的状态保存在git数据库中
(1)创建树对象(文件已提交数据库)
Git根据某一个暂存区所表示的状态记录一个对应的树对象,git的暂存区是一个文件:.git/index
这里要注意将file文件放在.git
文件外,否则无法创建暂存区(上面的操作我都把file.txt放在了.git内🤧),大概错误如下
创建暂存区
1 | git update-index --add file.txt |
查看暂存区的内容
1 | git ls-files --stage |
将暂存区的内容写入一个树对象
1 | git write-tree |
这时候再查看git数据库,树对象也被存入git数据库内了
再查看该记录存储的内容
1 | git cat-file -t 79335 |
可以看到树对象中存储着文件名与文件内容对应的哈希值等
(2)创建对象(文件未提交数据库)
上面我们是添加一个已经存在再git数据库的文件到暂存区,如果我们新建一个未曾保存到git数据库的文件到暂存区,进而保存为tree对象
- 我们会发现新文件提交到暂存区后也会自动添加到数据库
- 添加文件到暂存区是追加操作,变更提交后,暂存区并没有清空
(3)子文件夹保存到树对象
1 | mkdir new_dir |
可以发现,我们无法将一个空文件夹添加到暂存区,并且提示我们应该将文件夹中的文件加入暂存区
接下来我们就试一下将文件夹中的文件添加进缓存区
可以发现文件夹是以一个tree对象添加进树中的
所以git的树对象的组织方式大致如下
树对象相当于源代码的一次次快照,因此我们可以用树对象作为源代码版本管理,接下来我们还需要解决记录谁提交了代码、什么时候提交的、提交的说明信息等的问题,这就需要用到提交对象
6.Git的提交对象
(1)创建提交对象
将创建的树对象提交为commit对象
1 | git write-tree |
查看提交对象的内容
1 | git cat-file -p dd7b6 |
我们可以看到一个提交对象包含提交版本的树对象hash键值,author和commiter,以及修改和提交时间,以及提交的注释信息
(2)提交新版本
生成新的文件的对象树
1 | echo "new version" > file.txt |
在上一次提交的基础上提交对象树作为提交对象
1 | git commit-tree a4098 -p dd7b6 -m "second commit" |
-p
对应上次提交对象的hash值
查看提交对象,可以看到多了一个父提交的记录
(3)提交对象记录
通过git log 查看提交对象记录
1 | git log 98b0 |
至此我们可以看到git对象之间联系的全貌了
7.Git的引用
以上的操作,我们对版本和数据对象的操作都是基于hash键值的,但是这些字符串是毫无意义的,所以git引入了引用(reference)使用有意义的字符串对应哈希值来解决这个问题,其主要应用于分支
(1)创建引用
为最新的提交对象创建一个引用名为master
1 | git log 98b0 |
(2)查看
创建成功后,我们就可以使用master来代替hash值查看提交对象记录
这就是git分支的本质:一个指向某一系列提交之首的指针
(https://raw.githubusercontents.com/Autovy/Image/master/img/202111162329835.png)
这就是git分支的本质:一个指向某一系列提交之首的指针