Git 总结篇

什么是 Git ?

Git 是一个开源的分布式 版本控制系统,可以快速地处理从很小到非常大的项目版本管理。版本控制是一种记录一个或若干文件内容变化,以便将来查阅特定版本修订情况的系统。

Git 的来由?

Git 官网中有提到 --- Git - Git 简史 (git-scm.com)

Git 是由 Linux 之父 --- Linus torvalds 创作的,创作 git 是为了管理 Linux 内核的开发。由于 Linux 的代码编写有很多人的参与,并且当时是通过手工的方式来进行的代码合并。而随着 Linux 的持续发展,其代码库变得比较庞大,没有办法继续通过手工的方式去进行管理,在当时选择了一个商业版的版本控制系统 Bit keeper 来进行代码的管理,并且当时还给 Linux 社区授予了免费使用权。几年之后, Linux 内核社区与 bit keeper 商业公司关系破裂,并且也撤销了 Linux 内核社区的免费使用权。然后 Linus 决定自己去开发一个版本控制系统,然后就有了 Git

Git (分布式) 与 SVN (集中式) 的区别

Git 官网中有提到 --- Git - 关于版本控制 (git-scm.com)

集中式的 SVN

  • 管理员可掌握其开发权限
  • 服务器单点故障
  • 容错性差

分布式的 Git

  • 本地代码仓库存在完整的历史记录
  • 代码保密性差(每个人都有完整的代码版本,警惕工作中可能会产生的意外提交)

Git 原理

.git 文件目录

git init 之后,会在当前目录下新建一个 .git 的文件夹,基本结构如下:

bash 复制代码
./.git
./.git/config
./.git/description
./.git/HEAD
./.git/hooks
./.git/info
./.git/info/exclude
./.git/objects
./.git/objects/info
./.git/objects/pack
./.git/refs
./.git/refs/heads
./.git/refs/tags

.git/config

可以通过 git config user.name "xx" 令来覆盖全局的 user 配置。全局的 git 配置文件目录是~/.gitconfig。配置全局的需要加上 global:git config --global user.name "xx"

.git/hook

hooks 目录包含客户端或服务端的钩子脚本(hook scripts),用于在 git 命令前后做检查或做些自定义动作。

.git/info

info 目录包含一个全局性排除(global exclude)文件, 用以放置那些不希望被记录在 .gitignore 文件中的忽略模式(ignored patterns)。

.git/objects

objects 目录存储所有数据内容(git 的数据库)。通过 git cat-file -t <校验和> 可以查看其中文件的类型,类型包括【blob | tree | commit】三种,-s 可以查看内容的长度,-p可以查看到文件具体的内容。

.git/refs

refs 目录存储指向数据(分支、远程仓库和标签等)的提交对象的指针。

.git/HEAD

HEAD 文件指向目前被检出的分支。

bash 复制代码
$ cat .git/HEAD
ref: refs/heads/master

.git/index

index 文件保存暂存区信息。通过 git ls-files 可以查看到暂存区中的所有文件。git ls-files -s可以查看除文件以外的更多信息。如:100644 190a18037c64c43e6b11489df4bf0b9eb6d2c9bf 0 t1.txt

Git 存储

Git 是一个内容寻址文件系统,其存储内容都是通过内容地址维护,可以把它理解成一个键值对存储方式:即给定一个存储文件,该系统根据文件信息和内容,使用 SHA-1 算法计算,返回一个由 40 个十六进制字符(0-9 和 a-f)组成的唯一字符串,之后只需要通过该字符串(键)即可访问该文件,这个字符串就是 Git 中通常所说的校验和。

Git 对象

git add 暂存操作 会为每一个文件计算校验和(SHA-1 哈希算法),然后会把当前版本的文件快照保存到 Git 仓库中 (Git 使用 blob 对象 来保存它们)。当使用 git commit 进行提交操作 时,Git 会先计算每一个子目录的校验和,然后在 Git 仓库中将这些校验和保存为树对象 。随后,Git 便会创建一个提交对象, 它除了包含上面提到的那些信息外,还包含指向这个树对象(项目根目录)的指针。

📌 Git 对象一旦创建,不可变更。
Blob 对象

数据对象,git add 将文件加入暂存区时产生,保存着文件快照。如下:

shell 复制代码
$ echo '123' > t1.txt                                   # 将123输入到 t1.txt 文件
$ git add t1.txt
$ find .git/objects -type f                             # 查看.git/objects目录下的所有文件
.git/objects/19/0a18037c64c43e6b11489df4bf0b9eb6d2c9bf
$ git cat-file -p 190a                                  # 查看该HASH值对应的具体内容
123
$ git cat-file -t 190a                                  # 查看该HASH值存储的文件类型
blob
$ git hash-object t1.txt                                # 返回存储在 Git 仓库中的唯一键
190a18037c64c43e6b11489df4bf0b9eb6d2c9bf

若添加的不同文件中的内容相同时,只会产生一个 blob 类型的文件。因为 blob 对象存储的只有文件的内容,而且 SHA-1 计算校验和时也是针对文件中具体的内容,所以相同的内容计算出相同的校验和是必然的。

Tip: 计算文件具体内容的校验和时,会在最前面加上 blob 和文件的长度,如:blob 10\0123"\0" 代表字符串的结束符。

Tree 对象

树对象,记录着目录结构和 blob 对象索引,解决文件名的存储问题。为 tree 对象类型的文件中所保存的信息,例如下:

css 复制代码
$ git cat-file -p c114
100644 blob 190a18037c64c43e6b11489df4bf0b9eb6d2c9bf    t1.txt

commit 对象

提交对象,包含一个树对象、【上一个 commit 】以及提交者的信息和注释,通过包含的树对象就可以找到所有提交的数据对象,从而形成一个 git 版本。为 commit 对象类型的文件中所保存的信息,例如下:

1️⃣ 进行第一次commit提交之后:

ruby 复制代码
# 第一次 commit 
$ git cat-file -p abdc
tree c1140d41172dce873fcf6177e8bd20b034fd9fba
author duqian01 <duqian01@qianxin.com> 1642730112 +0800
committer duqian01 <duqian01@qianxin.com> 1642730112 +0800

lst commit

$ git cat-file -p c114
100644 blob 190a18037c64c43e6b11489df4bf0b9eb6d2c9bf    t1.txt

2️⃣ 进行第二次commit提交之后:

sql 复制代码
# 修改t1.txt进行第二次 commit后,会多一个 parent 指针,它指向的就是上一次提交的commit 对象
$ git cat-file -p 3bc4
tree e601a5808de751f128820189e03a1ec94a9fd160
parent abdc97446af96108f709480a5996ce81339120e2
author duqian01 <duqian01@qianxin.com> 1642730924 +0800
committer duqian01 <duqian01@qianxin.com> 1642730924 +0800

2nd commit

$ git cat-file -p e601
100644 blob 81c545efebe5f57d4cab2ba9ec294c4b0cadf672    t1.txt

3️⃣ 添加t3.txt文件之后,进行第三次commit提交:

sql 复制代码
# 添加t2.txt文件进行第三次提交,该commit对象中存在两个文件,其中t1.txt还是会用上一个commit中的
$ echo '12345' > t2.txt
$ git add t2.txt
$ git commit -m '3rd commit'
$ git cat-file -p 23e3
tree 417b026c3dbbb53a96e10a62a72fa3df0f052131
parent 3bc4b63ddd6199a2fab2545da5eae88d9fb24e02
author duqian01 <duqian01@qianxin.com> 1642733174 +0800
committer duqian01 <duqian01@qianxin.com> 1642733174 +0800

3rd commit

$ git cat-file -p 417b
100644 blob 81c545efebe5f57d4cab2ba9ec294c4b0cadf672    t1.txt
100644 blob e56e15bb7ddb6bd0b6d924b18fcee53d8713d7ea    t2.txt

4️⃣ 新建一个目录与文件后,进行第4次commit提交:

shell 复制代码
# 新建一个目录与文件后,进行第4次commit提交
$ mkdir d1
$ cd d1/
$ echo '123456' > t3.txt
$ cd ..
$ git add d1/
$ git commit -m '4th commit'
$ git cat-file -p 4d3f
tree 0a9b004d5dfc0f3539a2d80678356683d0b136e7
parent 23e311bd307c7f92414dadf2089fec27a0ebe591
author duqian01 <duqian01@qianxin.com> 1642734614 +0800
committer duqian01 <duqian01@qianxin.com> 1642734614 +0800

4th commit

$ git cat-file -p 0a9b
040000 tree 684add08d1249cfde1071ba3fc8272238c960ac4    d1
100644 blob 81c545efebe5f57d4cab2ba9ec294c4b0cadf672    t1.txt
100644 blob e56e15bb7ddb6bd0b6d924b18fcee53d8713d7ea    t2.txt

此时.git/objects的目录结构如下:

lua 复制代码
$ tree .git/objects/
.git/objects/
|-- 0a
|   `-- 9b004d5dfc0f3539a2d80678356683d0b136e7
|-- 19
|   `-- 0a18037c64c43e6b11489df4bf0b9eb6d2c9bf
|-- 23
|   `-- e311bd307c7f92414dadf2089fec27a0ebe591
|-- 3b
|   `-- c4b63ddd6199a2fab2545da5eae88d9fb24e02
|-- 41
|   `-- 7b026c3dbbb53a96e10a62a72fa3df0f052131
|-- 4d
|   `-- 3f8671088fbcb20749ce029ad58302af25a54e
|-- 68
|   `-- 4add08d1249cfde1071ba3fc8272238c960ac4
|-- 81
|   `-- c545efebe5f57d4cab2ba9ec294c4b0cadf672
|-- 9f
|   `-- 358a4addefcab294b83e4282bfef1f9625a249
|-- ab
|   `-- dc97446af96108f709480a5996ce81339120e2
|-- c1
|   `-- 140d41172dce873fcf6177e8bd20b034fd9fba
|-- e5
|   `-- 6e15bb7ddb6bd0b6d924b18fcee53d8713d7ea
|-- e6
|   `-- 01a5808de751f128820189e03a1ec94a9fd160
|-- info
`-- pack

15 directories, 13 files

Git 对象的压缩

每次 commit,即便是很小的改动,Git 存储的都是全新的文件快照。git 中有对象的压缩机制,通过执行 git gc 可以将对象进行压缩。压缩后的文件会存储到 .git\objects\pack 目录中:其中以 .pack 结尾的文件是包文件,这个文件包含了从文件系统中移除的所有对象的内容; 以 .idx 结尾的文件是索引文件,这个文件包含了包文件的偏移信息。可以通过 git verify-pack -v <path> 来查看已打包内容。

git 对象被压缩之后,仍然能够通过对象 hash 值来获得其具体的存储内容。

bash 复制代码
$ git gc
Enumerating objects: 16, done.
Counting objects: 100% (16/16), done.
Delta compression using up to 8 threads
Compressing objects: 100% (8/8), done.
Writing objects: 100% (16/16), done.
Total 16 (delta 2), reused 0 (delta 0), pack-reused 0

$ tree .git/objects/pack/
.git/objects/pack/
|-- pack-b3d65c905509214afb33da26b6ce2284b4d40c9c.idx
`-- pack-b3d65c905509214afb33da26b6ce2284b4d40c9c.pack

0 directories, 2 files

$ git verify-pack -v .git/objects/pack/pack-b3d65c905509214afb33da26b6ce2284b4d40c9c.idx

Git 对象的解压缩

通过 git unpack-objects < <path> 可以将被压缩的 .pack 文件进行解压,需要主要的是,不可以在当前目录下进行解压,可以将 .pack 压缩文件移动(mv)到 .git/objects 目录下之后再进行解压。

Git 垃圾对象的清理

什么是垃圾对象?

若一个文件进行多次修改并添加到暂存区,创建了多个 git blob 对象,将该文件进行 commit 提交。此时,去执行 git gc 压缩命令后存在的未被进行压缩的对象。例如下:

sql 复制代码
$ touch t1.txt
$ echo 'abc' > t1.txt
$ git add t1.txt
$ echo 'abcd' > t1.txt
$ git add t1.txt
$ echo 'abcde' > t1.txt
$ git add t1.txt
# 此时查看 objects 目录
$ tree .git/objects/
.git/objects/
|-- 00
|   `-- dedf6bd5f3e493ce8b03c889912f47b01297d4
|-- 8b
|   `-- aef1b4abc478178b004d62031cf7fe6db6f903
|-- ac
|   `-- be86c7c89586e0912a0a851bacf309c595c308
|-- info
`-- pack

5 directories, 3 files

$ git commit -m 'first commit'
# 进行 commit 提交之后,objects 目录会多增加两个对象文件
$ tree .git/objects/
.git/objects/
|-- 00
|   `-- dedf6bd5f3e493ce8b03c889912f47b01297d4
|-- 8b
|   `-- aef1b4abc478178b004d62031cf7fe6db6f903
|-- a8
|   `-- f47426fce77344845c02c4f32d04db7c12a2ae
|-- ac
|   `-- be86c7c89586e0912a0a851bacf309c595c308
|-- e9
|   `-- 0cefdcfb7e54748efab54e94833515b8754eff
|-- info
`-- pack

7 directories, 5 files

$ git gc
Enumerating objects: 3, done.
Counting objects: 100% (3/3), done.
Writing objects: 100% (3/3), done.
Total 3 (delta 0), reused 0 (delta 0), pack-reused 0

$ tree .git/objects/
.git/objects/
|-- 8b
|   `-- aef1b4abc478178b004d62031cf7fe6db6f903
|-- ac
|   `-- be86c7c89586e0912a0a851bacf309c595c308
|-- info
|   |-- commit-graph
|   `-- packs
`-- pack
    |-- pack-2cbc97311f4d9d1be5a68822b7d66e6b7589d47b.idx
    `-- pack-2cbc97311f4d9d1be5a68822b7d66e6b7589d47b.pack

4 directories, 6 files

如何清理?

可以通过执行 git prune 命令来删除垃圾对象,在执行该命令之前可以先通过 git prune -n | git fsck 命令去查看 Git 存在哪些垃圾对象。

Git 状态

Git 的三种状态: 已提交 (committed)、已修改 (modified) 和 已暂存(staged)。

  • 已提交表示数据已经安全地保存在本地数据库中 → 对应 Git 本地仓库目录。
  • 已修改表示修改了文件,但还没保存到数据库中 → 对应工作区。
  • 已暂存表示对一个已修改文件的当前版本做了标记,使之包含在下次提交的快照中 → 对应暂存区。

此外,还存在一种未跟踪的文件状态,它们既不存在于上次快照的记录中,也没有被放入暂存区。

远程仓库的添加

执行 git remote add origin 之后,会在 .git/config 文件中配置一个名字为 origin 的远程仓库。首次执行 git push -u origin master 之后,会在 .git 文件中新增 4个目录与 2 个文件:refs 目录中增加 remote/origin/master;logs/refs 目录中增加 remote/origin/master。其中在 .git/refs 目录的 master 文件中存储的是最新一次提交到远端的 commit 对象 hash 值。

Git 基本工作流程

本地仓库

为什么存在暂存区?

  • 一个扩展的选择性提交概念
  • 为方便于git命令行操作而进行设计,对文件的改动进行分组提交

远程仓库

Git 协议

Git 协议

Git 官方文档中提到 --- Git协议

Git 有四种不同的传输协议:本地协议(Local),HTTP 协议,SSH(Secure Shell)协议及 Git 协议。其中,工作中常用到的就是 SSH 协议。

Git 分支

分支?

Git 官方文档中提到 --- Git分支简介

分支:由每次提交的代码,串成的一条时间线。Git 的分支,其实本质上仅仅是一个指向某一系列提交之首的指针或引用。

为什么需要分支?

  • 需求迭代
  • 定制版项目
  • 历史遗留版本 bug 修复
  • 尝试性的模块功能开发 ......

Git 分支的新建与合并

进行 git branch testing 创建分支时,就是创建了一个可以移动的新的指针,会在当前所在的提交对象上创建一个指针。可以通过 HEAD 指针来获得当前在哪个分支上。

git checkout testing 切换分支之后,当前所使用的分支就是 testingHEAD 就会指向 testing 分支。若此时再将 master 分支切回,会做两件事:一是使 HEAD 指回 master 分支,二是将工作目录恢复成 master 分支所指向的快照内容。

git merge testing 进行分支合并,这时 master 指针与 Head 指针就会指向 testting 的位置,并且这次合并是一个 快进(fast-forward)。有时候合并操作不会如此顺利。 如果你在两个不同的分支中,对同一个文件的同一个部分进行了不同的修改,Git 就没法干净的合并它们,在合并它们的时候就会产生合并冲突。

遇到冲突时的分支合并

当合并遇到冲突时,Git 会暂停下来,等待你去解决合并产生的冲突。并且可以使用 git status 来查看那些因包含合并冲突而处于未合并(unmerged)状态的文件。另外,产生冲突的文件会包含特殊字符<<<<<<<。手动解决冲突之后,对文件使用 git add 命令来将其标记为冲突已解决。

快进(fast-forward)

由于想要合并的分支 hotfix 所指向的提交 C4 是你所在的提交 C2 的直接后继, 因此 Git 会直接将指针向前移动。换句话说,当你试图合并两个分支时, 如果顺着一个分支走下去能够到达另一个分支,那么 Git 在合并两者的时候, 只会简单的将指针向前推进(指针右移),因为这种情况下的合并操作没有需要解决的分歧------这就叫做 "快进(fast-forward)"。

变基

Git 官方文档中提到 --- Git - 变基

合并分支的方法

merge

merge 命令。 它会把两个分支的最新快照(C3C4)以及二者最近的共同祖先(C2)进行三方合并,合并的结果是生成一个新的快照(并提交)。

rebase

这两种整合方法的最终结果没有任何区别,但是变基使得提交历史更加整洁。 你在查看一个经过变基的分支的历史记录时会发现,尽管实际的开发工作是并行的, 但它们看上去就像是串行的一样,提交历史是一条直线没有分叉。

变基是将一系列提交按照原有次序依次应用到另一分支上,而合并是把最终结果合在一起。

变基的风险

📌 如果提交存在于你的仓库之外,而别人可能基于这些提交进行开发,那么不要执行变基。

变基操作的实质是丢弃一些现有的提交,然后相应地新建一些内容一样但实际上不同的提交。 如果你已经将提交推送至某个仓库,而其他人也已经从该仓库拉取提交并进行了后续工作,此时,如果你用 git rebase 命令重新整理了提交并再次推送,你的同伴因此将不得不再次将他们手头的工作与你的提交进行整合,如果接下来你还要拉取并整合他们修改过的提交,事情就会变得一团糟。

Git 规范

Git commit 规范

Commit日志规范一般包括:Header(类型, 改动范围,精简总结)、Body、footer。

简单参考规范: <type>: <JIRA ID> <subject>

type: 本次修改的动作类型,可分为:

  • feat:新增 xxx 功能
  • fix:修复 xxx Bug
  • docs:变更 xxx 文档
  • style:变更 xxx 代码格式或注释,注意不是 css 修改
  • refactor:重构 xxx 功能或方法
  • test: 更新测试代码
  • chore: 构建过程或辅助工具的变动
  • perf: 性能优化(不涉及功能变更)
  • build: 建议使用 chore
  • workflow: 建议使用 chore
  • ci: 建议使用 chore

JIRAID: 可以跟踪需求、缺陷

subject: commit 的概述,建议不超过50个字符

Body: 本次commit的详细描述,可以分成多行,建议以72个字符换行

xml 复制代码
    <type>: <JIRA ID> <subject>
    <BLANK LINE>
    <body>
    <BLANK LINE>
    <footer>

原子性提交

原子性:在一个大型系统中,形成一个不可分割的最简单元或组件。

当代码变动时你想创建提交时,这个提交应该尽可能的小量,并且包含一个不可分割的特性(feature)、修复(fix)或优化(improved)。

原子性提交的好处:

  • Code reviews会更简单
  • 更加容易回滚

Git Flow 规范

Git Flow 工作流定义了一个围绕项目发布的严格分支模型,为不同的分支分配一个很明确的角色。

  • Master分支:存储正式发布的历史
  • Develop分支:开发分支(功能的集成分支)
  • Feature分支:功能分支,由develop分支作为父分支,新功能完成后,合并回develop分支
  • Release分支:发布分支(预发布版本),从develop分支拉出,该分支不再加入新功能,可做bug修复,测试成功之后合并到master分支并分配好一个版本号打好tag
  • Hotfix分支:维护分支(热修复),用于快速给产品发布版本打补丁,基于发布的上一个版本来创建临时分支,修复完成后直接合并到master的下一个tag
相关推荐
int WINGsssss5 小时前
Git使用
git
用户0760530354387 小时前
Git Revert:安全移除错误提交的方式
git
Good_Starry19 小时前
Git介绍--github/gitee/gitlab使用
git·gitee·gitlab·github
云端奇趣1 天前
探索 3 个有趣的 GitHub 学习资源库
经验分享·git·学习·github
F_D_Z1 天前
【解决办法】git clone报错unable to access ‘xxx‘: SSL certificate problem:
网络·git·网络协议·ssl
等风来不如迎风去1 天前
【git】main|REBASE 2/6
git
艾伦~耶格尔1 天前
IDEA 配置 Git 详解
java·ide·git·后端·intellij-idea
云兮杜康1 天前
IDEA中用过git之后文件名颜色全变红
java·git·intellij-idea
睡不醒的小泽1 天前
git merge 和 git rebase
git
艾伦~耶格尔1 天前
Git 下载及安装超详教程(2024)
git·gitee·码仓