0%

开发工具|Git的使用与工作原理

img

参考文章:

深入理解Git实现原理

Git内部原理之Git对象

Git 误操作救命良方

Git教程

Git基本知识

1.Git定义

git的本质就是要实现对文件变更过程的存储

Git是一种分布式版本控制系统:

  • 版本控制:git可以对文件变更过程进行管理,随时可以拿出所需要的版本
  • 分布式:Git的数据不仅存在远程仓库(即Git平台的服务器),也存在本地仓库(即项目开发者的本地计算机),所以称为分布式

2.Git功能列表

  • 我们可以为每一次变更提交版本更新并且备注更新的内容
  • 我们可以在项目的各个历史版本之间自如切换
  • 我们可以一目了然的比较出两个版本之间的差异
  • 我们可以从当前的修改中撤销一些操作
  • 我们可以自如的创建分支、合并分支
  • 我们可以和多人协作开发
  • 我们可以采取自由多样的开发模式

3.Git基本术语

img

  • 工作区:在编辑器内可增加或修改文件等位置

  • 缓存区:提交代码,解决冲突的中转站

  • 版本库:本地.git/目录,内含缓存区

  • 本地仓库:连接本地代码和远程代码的枢纽,在没有联网状态下,本地代码可先提交至该处

  • 远程仓库:托管远程代码的中央服务器

4.Git托管平台

(1)Github
img

网址:https://github.com/

Gihub是全球最流行的代码托管平台,拥有大量非常知名的开源项目。github的服务器部署在国外,国内访问不友好,适合用于学习和开发开源项目

(2)Gitlab
img

网址:https://about.gitlab.com/

Gitlab支持免费搭建私有库,功能强大,对企业级的内部开发友好,较为笨重不适合用于简单项目的搭建

(3)Gitee

logo_gitee_light (1)

网址:https://gitee.com/

俗称码云,国内知名的在线代码托管平台,基于gitlab

(4)Coding
img

网址:https://coding.net/

Coding 是一个面向开发者的云端开发平台,提供git/svn 代码托管,并且面向个人提供私有库。

对国内小团队开发较为友好,个人比较推荐,下面的一些操作都会基于Coding平台

Git快速入门

1.Git安装

git官网地址:https://git-scm.com/

image-20200629224316821

image-20200629224329147

image-20200629224339198

image-20200629224349811

image-20200629224400914

image-20200629224411947

image-20200629224425378

image-20200629224436646

image-20200629224446392

2.Git密钥

本地仓库与远程仓库通过SSH公私钥进行身份确认,

(1)生成公钥

终端输入命令生成公钥

1
ssh-keygen -o

连续三次回车使用默认设置

image-20211112233436546

(2)查看公钥

旁边的id_rsa存的就是私钥

1
cat 【公钥保存位置】/id_rsa.pub

image-20211112234254250

(3)上传公钥

打开coding的个人设置页面即可上传公钥

image-20211112234911836

(4)Git连接测试

通过以下命令即可测试ssh连接是否成功(第一次连接需要一个输入yes的步骤)

1
ssh -T git@e.coding.net

image-20211112235520804

3.Git项目创建实战

(1)Coding上创建项目

点击创建项目

image-20211112235849036

选择模板2中的代码托管即可

image-20211113000450682

填写项目基本信息,即可完成创建

image-20211113000842644

(2)创建代码仓库

在项目下创建代码仓库

image-20211113001242845

基本设置如下:

image-20211113001619059

(3)将项目克隆到本地

复制仓库的ssh链接,将一个仓库内的代码使用ssh下载到本地

image-20211113001922314

电脑上执行git clone命令,将远程仓库克隆为本地仓库

1
git clone 【SSH链接】

image-20211113004407422

有时会出现下载时权限不足的情况,但是ssh的连接测试又能成功,这时候可以尝试一下重新提交公钥

进入项目文件夹

image-20211113004735237

在git bash下可以看到Test文件有master的符号

image-20211113004836580

(4)设置签名

设置签名以区分不同开发者的身份(与代码托管中心的账号,密码无关)

1
2
git config user.name "Autovy"
git config user.email "autovys@gmail.com"

image-20211113005344281

设置完成后,即可在配置文件看到相关信息

1
cat ./.git/config

image-20211113005519211

(5)代码提交流程

img

修改项目文件

创建一个html文件

1
echo "test" > index.html

修改md文件

image-20211113104443895

将工作区改动内容提交到缓存区

1
git add ./

查看工作区和缓存区状态

可以看到文件改动情况

1
git status

image-20211113010859895

将缓存区的内容提交到本地仓库

1
git commit -m "v1.1:添加index文件"

image-20211113011106112

查看历史记录

1
git log

image-20211113011224760

将本地仓库的内容推送到远程仓库

1
git push

image-20211113011323568

最后可以看到Coding仓库变化

image-20211113012003368

Git高级使用

1.Git版本管理

(1)更新1.2为最新版

重复上面步骤,修改并更新index.html文件

1
vim index.html

image-20211113085631627

修改md中的版本信息

1
vim README.md

image-20211113085813333

将1.2版本的代码按步骤提交到本地仓库

1
2
git add ./
git commit -m "v1.2:增加index文件标题"

这里先不推送远端仓库,方便后续切换版本

(2)查看日志

git log 查看提交日志

1
git log

image-20211113090846535

commit后面一大段的字符串是commit id(版本号),这里HEAD是Git指向当前版本的指针,所以要切换版本就要移动HEAD到指定版本

(3)修改未添加缓冲区

修改index.html文件

1
vim index.html

image-20211113123920560

在对文件使用add ./将项目添加缓存区之前,我反悔了,决定丢弃工作区的修改

查看当前状态,可以看到提示使用git restore丢弃修改

1
git status

image-20211113124311037

丢弃修改工作区的修改,文件恢复到最近一次git commitgit add时的状态

1
git restore ./

image-20211113124505502

(4)修改添加到缓存区

修改index.html文件

1
vim index.html

image-20211113123920560

添加修改到缓冲区

1
add ./

查看当前状态,可以看到提示使用git restore --staged丢弃缓存内容

1
git status

image-20211113125325665

丢弃缓存内容后,再按上一种情况的流程恢复工作区

1
2
3
git restore --staged ./
git status
git restore ./

image-20211113125708241

(5)修改提交到本地仓库

将版本回退到上一个版本即1.1

1
git reset --hard HEAD^

image-20211113091804033

这时本地仓库就回退到了1.1版本

image-20211113092137541

这时候查看git log,可以发现1.2的提交记录就移除

image-20211113102703410

如果又需要回到1.2版本

先通过git reflog查看历史命令,可以找到1.2版本的id

1
git reflog

image-20211113102934666

同样可以使用git reset切换版本

1
git reset --hard 【commit id】

image-20211113103418305

这里的commit id不用填完整,填到后面的字母部分

这时本地仓库又切回了1.2

image-20211113103512977

【注意】

如果已经将最新的代码推送过远端仓库(即远端仓库有最新的版本,要保持本地和远端的资源一致),会报以下错误、

image-20211113103733311

(6)修改推送到远程仓库
1
git revert HEAD --edit //并修改提交信息

image-20211113095811776

这时后查看记录git log,可以看到撤销的操作也被记录其中

image-20211113095828085

这时本地仓库就是回退到了版本1.1

image-20211113100356269

直接推送到远程仓库,远程仓库也回退到1.1版本

image-20211113100641582

image-20211113100811889

如果提交了1.1版本后,又需要回到1.2版本,这时候只要撤销掉对1.2的撤销即可

image-20211113101733694

再git push推送到远程仓库即可

image-20211113102000137

(7)总结

img

  • 工作区修改后:git restore ./恢复工作区
  • git add ./添加到缓冲区后:git restore --staged ./ 恢复缓存区
  • git commit提交到本地仓库后:git reset --hard HEAD^恢复上个版本到缓存区
  • git push推送到远程仓库后:git revert HEAD --edit撤销上一个推送操作,并恢复到缓存区

2.Git分支管理

(1)分支解决的问题

image-20200728190230431

同时并行推进多个功能开发,提高开发效率

各个分支在开发过程中,如果某一个分支开发失败,不会对其他分支有任何影响。失败的分支删除重新开始即可

最后再将分支合并到master上

分支:

git-br-dev-fd

分支的合并(默认快速模式):

git-br-ff-merge

分支的合并(普通模式):

git-no-ff-mode

(2)分支基本操作

创建dev分支

1
git branch dev

git checkout -b dev可以创建并切换到dev分支

查看当前分支

1
git branch

image-20211113173909862

可以看到dev分支已经创建成功了,而当前位于master分支

切换到dev分支

1
git switch dev

image-20211113174105689

修改文件内容

1
vim README.md

image-20211113174328787

将最新修改的内容提交到dev分支

1
2
git add ./
git commit -m "dev new test"

image-20211113174453991

切换回master分支,并查看文件内容

1
2
git switch master
cat README.md

image-20211113174734151

可以看到主分支上没有dev的内容

合并分支

在master分支上合并dev

1
git merge dev

image-20211113190746514

可以看到在dev上的修改在master上生效了

(3)分支合并冲突问题

前面的情况中,dev是在master基础上的,合并前master并没有修改,如果master再合并前与dev修改了同一行的内容就会出现冲突

先撤销一下合并

查看命令记录,可以看到合并记录中合并的方式是快速模式(即直接把master指向dev的当前提交,所以合并速度非常快)

1
git reflog

image-20211113191710611

撤销合并(未推送)

1
git reset --hard HEAD^

image-20211113192813592

修改master内容,并提交到本地仓库

1
vim README.md

image-20211113193624097

1
2
git add ./
git commit -m "master new test"

image-20211113193330217

再次合并dev分支

1
git merge dev

这时就会出现合并冲突的问题

image-20211113193742100

解决冲突

编辑README.md文件

1
vim README.md

删除特殊符号,修改内容

image-20211113194812446

然后正常提交到master分支即可

1
2
git add ./
git commit -m "master+dev"

image-20211113195019152

一般来说不会再本地仓库库对分支进行合并,而是在远程的代码平台进行分支的合并

推送内容到master

1
git push 

image-20211113195444076

由于远程仓库还没有创建dev分支,所以推送时要创建分支

1
git push --set-upstream origin dev

这时即可在Coding上看到两个分支了

image-20211113195934598

代码平台上也可以合并分支,当如果有冲突就无法合并

image-20211113200419162

(3)分支中的多人协作

准备wsl模拟另一台电脑,一个新的coding账号,新的coding账号提交wsl中的ssh,然后使用两个浏览器登录分别登录两个账号

将第二个账号(二号机)邀请到主账号的团队中

image-20211113215154747

将二号机拉入项目中

image-20211113215314621

二号机wsl看到项目后克隆项目到本地

1
git clone git@e.coding.net:autovys/demo/Test.git

image-20211113215727175

进入目录后查看分支,可以发现只能看到master分支

1
git branch

image-20211113220115181

二号机要在dev基础上进行开发,就要创建远程origindev分支到本地

1
git checkout -b dev origin/dev

image-20211113220445123

二号机在dev基础上修改了文件内容

1
vim README.md

image-20211113221049407

并且二号机还推送到了远程仓库

1
2
3
git add ./
git commit -m"edit something"
git push

image-20211113221447824

image-20211113221631576

这时一号机也要对同样的文件修改一些内容,并尝试推送到dev分支

1
2
git switch dev
vim README.md

image-20211113223622153

1
2
3
git add ./
git commit -m "add someting"
git push

image-20211113223826310

可以发现远程推送失败了,因为二号机的最新提交和我推送的提交有冲突

这时就需要先git pull把最新的提交从origin/dev抓下来,然后,在本地合并,解决冲突,再推送

1
git pull

image-20211113224245383

这时候解决的方法和分支管理中的解决冲突完全一样

image-20211113224352026

编辑README.md文件

1
vim README.md

删除特殊符号,修改内容

然后正常提交到dev分支即可

1
2
3
git add ./
git commit -m "dev lastest"
git push

image-20211113224806916

IDEA中的Git

1.基本代码提交流程

(1)创建空项目

在Coding上创建新的空的代码仓库

image-20211115152050857

idea克隆项目到本地

image-20211115153822099

填写远程仓库的SSH地址和下载的本地位置完成下载

image-20211115153943646

(2)IDEA打开项目文件

IDEA打开项目文件后,可以看到IDEA自动识别了git,出现了以下的一些工具

image-20211115152526919

image-20211115152549962

(3)代码提交流程

修改README.md文件如下,即在工作区改动了代码

image-20211115154732860

将代码添加到缓冲区(git add ./),这一步可以省略,直接提交idea会帮你做这一步操作

image-20211115155242753

提交代码到本地仓库,注意填写提交信息(git commit -m "message"

image-20211115155909225

最后推送到远程仓库,即完成一次代码提交(git push

image-20211115160154599

关于提交的一些信息我们都可以在git工具栏的控制台进行查看,可以看到git工具的控制台帮我们输入的命令内容

image-20211115160454918

2.Git版本管理

(1)更新版本到1.2

image-20211115160753928

(2)查看提交日志

image-20211115160933261

可以看到idea使用git不需要设置签名,而是默认使用了远程仓库平台的注册信息

(3)修改未添加缓冲区

代码未添加到缓冲区,需要恢复工作区的内容(简单呀,疯狂ctrl + Z 😎)

可以使用回滚,即可恢复到最近一次git commitgit add时的状态

image-20211115161519696

(4)修改添加到缓存区

版本1.2已经添加到了缓存区,同样可以通过上面的方法恢复工作区

(5)修改提交到本地仓库
image-20211115162500521

这时候可以看到回滚的按钮变灰了,这时我们可以打开git工具区对master分支进行操作,idea提供了三种还原的方法,都可以让我们恢复1.1版本

还原提交,是多加一步撤销的操作,恢复内容到本地仓库,可以直接提交

image-20211115163502070

撤销提交,是让1.2版本恢复到缓存区,这时候可以通过回滚恢复到1.1版本

image-20211115163801094

删除提交,直接恢复1.1版本到工作区

image-20211115164217388

所以推荐使用还原提交,如果要丢弃的版本就使用删除提交,否则idea无法恢复已撤销删除的提交(命令行可以恢复)

(6)修改推送到远程仓库

推送到远程仓库后,我们可以看到我们只剩下还原提交可以使用了,效果和上面的一样

image-20211115165440862

image-20211115165553230

idea还提供了将当前分支重置到此处的功能,其相当于进行多次的删除提交操作。在修改已提交到本地仓库的情况下,可以开始回溯到选择的版本;在修改已推送到远程的情况下可能会造成冲突问题,所以该功能慎重使用

image-20211115172549467)image-20211115173345841

(7)还原提交的问题

还原提交也可以还原更远的版本,但是会出现冲突问题,

image-20211115200310949

接收他们的则回滚到对应版本,合并则解决两个修改的冲突

image-20211115200355899

合并的页面如下,点击箭头即可自动修改冲突点

image-20211115200459979

3.Git分支管理

(1)分支基本操作

创建分支dev

image-20211115201204089

git工作区右键选择分支签出(checkout,什么铸币翻译)即可切换分支,注意记得先切换好分支再对提交的路径图进行操作,否则它提交的到的是当前的分支,即便是在另一条分支上做的操作

image-20211115202549168

这里在dev上操作的还原提交会被提交给master,所以要记得切换分支(这算不算一个小bug呢?),另外要删除一个分支时也要先切换到其他分支

image-20211115203232293

修改文件提交到dev

image-20211115201418871

直接选择推送,远程仓库就可以发现dev分支被创建好了

image-20211115215649882

合并master和dev分支,当前选择的是master分支,所以选择将当前合并到已选择(渣翻ks8)即为将dev合并到master

image-20211115214311537

image-20211115214548426

(2)分支合并冲突问题

先删除合并的提交恢复master,修改master的文件并提交

image-20211115214855710

这时候合并就会出现合并冲突现象

image-20211115215121774

前面两个是对master修改的内容与dev修改的内容做一个选择,选择合并则需要手动修改解决冲突

image-20211115215351531

点击箭头即可合并修改

image-20211115215421974

(3)分支中的多人协作

推送上面修改的master分支和dev分支,然后删除本地项目文件(即删除了本地仓库),进行一次remake

重新使用idea克隆项目到本地,可以看到本地只拉取了master分支

image-20211115220444280

这时候就需要右键点击远程的分支,新建dev分支到本地

image-20211115220650353

在Coding平台上改动dev分支的文件,模拟另一个开发人员在协同开发的情况下修改了文件

image-20211115221009031

这时候我们在本地也修改了dev分支文件并推送

image-20211115221205796

这时候idea会有弹窗提醒推送被拒绝,并在控制台提醒你用git pull

image-20211115221318639

idea是有git pull 这个按钮的

image-20211115221609871

但是点击更新到dev时,会提示你本地的dev没有追踪远程的分支

image-20211115221717207

点击提示选择上游分支同步远程的dev分支即可

image-20211115221849343

如果其他开发者新的更新和我更新的有冲突,这时候就是我们熟悉的冲突页面啦

image-20211115221925143

解决冲突后我们就可以推送最新版到远程仓库了,这样的机制保证了git协同开发的可能性

Git原理

Git本质是一个内容寻址的文件系统,其次才是一个版本控制系统

每次我们运行 git addgit commit 命令时, Git 所做的实质工作是将被改写的文件保存为数据对象,更新暂存区,记录树对象,最后创建一个指明了顶层树对象和父提交的提交对象

Git的核心是它的对象数据库(.git/objects文件),其中保存着git的对象:

  • blob对象:实现了对文件内容的记录
  • tree对象:实现了对文件名、文件目录结构的记录
  • commit对象:实现了对版本提交时间、版本作者、版本序列、版本说明等附加信息的记录

1.Git数据库

(1)Git文件结构

新建文件夹并将其初始化为一个空的git仓库

1
2
mkdir Test
git init Test

image-20211116163217085

进入git文件使用以下命令查看git仓库的文件结构

1
2
cd Test/.git
tree/F

image-20211116163406004

  • config是git基本配置文件
  • description是GitWeb专用的文件
  • info文件夹是全局性排除文件(与.gitignore互补)
  • hooks存放钩子脚本
  • HEAD记录当前checkout的分支
  • refs提交对象的指针
  • objects存放所有数据,这就是我们要找的数据库了
(2)Git数据库写入操作

Git会根据文件内容计算出一个hash值,以hash值作为文件索引存储在Git文件系统中

git hash-object可以用来计算文件内容的hash值,并将生成的数据对象存储到Git文件系统中

image-20211116193321631

1
echo "version 1" | git hash-object -w --stdin

-w表示将数据对象写入到Git文件系统中,--stdin表示从标准输入中获取文件内容

命令执行完成后返回一个哈希值,它就是git数据库中的键值(key),通过键值我们可以再次检索到插入数据库的内容(value),实际上git数据库就是一个简单的键值对数据库

查看object文件,可以看到新生成的文件,这个文件存储以新存入数据对应hash值前2位命名的文件夹内

image-20211116193416942

(3)Git数据库查询操作

既然我们拿到了键值,那肯定可以查询到其对应的内容,复制hash值进行查询

1
2
3
git cat-file -t d73e3875a8db6b402e2ce905c4a2222603c1f090

git cat-file -p d73e3875a8db6b402e2ce905c4a2222603c1f090

image-20211116193551166

-p表示查看Git对象的内容,-t表示查看Git对象的类型

blob对象是git数据库中的数据对象,此外数据库中还有树对象(tree)和提交对象(commit)

4.使用Git跟踪文件变更

接下来我们将模拟一次文件变更过程,看看git的对象数据库能不能实现“跟踪文件变更”的功能

(1)创建文件

创建一个txt文件写入内容“v1.1”,并写入到git数据库中

1
2
echo "version 1" > file.txt
git hash-object -w file.txt

image-20211116193749552

(2)修改文件

修改file.txt的内容,并再次提交到git数据库

1
2
echo "version 2" > file.txt
git hash-object -w file.txt

image-20211116193813071

可以看到这次的hash值已经发生改变了,而objetcs内会多出一个文件

image-20211116193846013

查询它们对应的内容如下

image-20211116193930700

说明我们对同一个文件修改后的内容也会经过git数据库的哈希处理再数据库生成新的数据对象,以此记录文件的不同版本

(3)恢复文件

所以我们想要把文件恢复到文件修改前版本,只需要在数据库取回即可,这就是git版本回滚的实质

1
2
3
cat file.txt
git cat-file -p 83baa > file.txt
cat file.txt

image-20211116194508507

数据对象只是解决了文件内容存储的问题,而文件名的存储则需要通过树对象来解决

5.Git的树对象

Git通过树对象(tree)将数据(blob)对象组织起来——其类型于一种文件系统:blob对应文件内容,tree对象对应的目录和节点

有了数对象,我们就可以将文件系统任何时间点的状态保存在git数据库中

(1)创建树对象(文件已提交数据库)

Git根据某一个暂存区所表示的状态记录一个对应的树对象,git的暂存区是一个文件:.git/index

这里要注意将file文件放在.git文件外,否则无法创建暂存区(上面的操作我都把file.txt放在了.git内🤧),大概错误如下

image-20211116201312604

创建暂存区

1
2
git update-index --add file.txt
cat .git\index

image-20211116201640442

查看暂存区的内容

1
git ls-files --stage

image-20211116201945864

暂存区的内容写入一个树对象

1
git write-tree

image-20211116202313249

这时候再查看git数据库,树对象也被存入git数据库内了

image-20211116202420483

再查看该记录存储的内容

1
2
git cat-file -t 79335
git cat-file -p 79335

image-20211116202635361

可以看到树对象中存储着文件名文件内容对应的哈希值

(2)创建对象(文件未提交数据库)

上面我们是添加一个已经存在再git数据库的文件到暂存区,如果我们新建一个未曾保存到git数据库的文件到暂存区,进而保存为tree对象

image-20211116205331283

  • 我们会发现新文件提交到暂存区后也会自动添加到数据库
  • 添加文件到暂存区是追加操作,变更提交后,暂存区并没有清空
(3)子文件夹保存到树对象
1
2
mkdir new_dir
git update-index --add new_dir

image-20211116205602679

可以发现,我们无法将一个空文件夹添加到暂存区,并且提示我们应该将文件夹中的文件加入暂存区

接下来我们就试一下将文件夹中的文件添加进缓存区

image-20211116210040965

可以发现文件夹是以一个tree对象添加进树中的

所以git的树对象的组织方式大致如下

img

树对象相当于源代码的一次次快照,因此我们可以用树对象作为源代码版本管理,接下来我们还需要解决记录谁提交了代码、什么时候提交的、提交的说明信息等的问题,这就需要用到提交对象

6.Git的提交对象

(1)创建提交对象

将创建的树对象提交为commit对象

1
2
git write-tree
git commit-tree 15da -m "frist commit"

image-20211116223537292

查看提交对象的内容

1
git cat-file -p dd7b6

image-20211116223657063

我们可以看到一个提交对象包含提交版本的树对象hash键值,author和commiter,以及修改和提交时间,以及提交的注释信息

(2)提交新版本

生成新的文件的对象树

1
2
3
echo "new version" > file.txt
git update-index file.txt
git write-tree

image-20211116224816724

在上一次提交的基础上提交对象树作为提交对象

1
2
git commit-tree a4098 -p dd7b6 -m "second commit"
98b0754a75a763db80ac689cd16e2665ced92589

-p对应上次提交对象的hash值

image-20211116224828288

查看提交对象,可以看到多了一个父提交的记录

image-20211116224957926

(3)提交对象记录

通过git log 查看提交对象记录

1
git log 98b0

image-20211116225317939

至此我们可以看到git对象之间联系的全貌了

img

7.Git的引用

以上的操作,我们对版本和数据对象的操作都是基于hash键值的,但是这些字符串是毫无意义的,所以git引入了引用(reference)使用有意义的字符串对应哈希值来解决这个问题,其主要应用于分支

(1)创建引用

为最新的提交对象创建一个引用名为master

1
2
git log 98b0
git update-ref refs/heads/master 98b0

image-20211116232811501

(2)查看

创建成功后,我们就可以使用master来代替hash值查看提交对象记录

image-20211116232919198

这就是git分支的本质:一个指向某一系列提交之首的指针

(https://raw.githubusercontents.com/Autovy/Image/master/img/202111162329835.png)

这就是git分支的本质:一个指向某一系列提交之首的指针