这是我参与「第三届青训营 -后端场」笔记创作活动的第 5 篇笔记
为什么要学习Git?
- 协同工作 业界绝大多数公司都是基于Git进行代码管理,因此Git是一个程序员的必备技能。
- 开源社区 目前绝大多数的开源项目都是基于Git维护的,参与这些项目的开发都需要使用Git。
01.Git是什么
Git is a free and open source distributed version control system designed to handle everything from small to very large projects with speed and efficiency.
1.1版本控制
| 版本控制类型 | 代表性工具 | 解决的问题 |
|---|---|---|
| 本地版本控制 | RCS | 本地代码的版本控制 |
| 集中式版本控制 | SVN | 提供一个远端服务器来维护代码版本,本地不保存代码版本,解决多人协作问题 |
| 分布式版本控制 | Git | 每个仓库都能记录版本历史,解决只有一个服务器保存版本的问题 |
1.1.1本地版本控制
RCS 基本原理:本地保存所有变更的补丁集,可以理解成就是所有的diff,通过这些补丁,我们可以计算出每个版本的实际的文件内容。
缺点:只能本地使用,无法进行团队协作。
1.1.2集中版本控制
代表性工具SVN
基本原理:
- 提供一个远端服务来保存文件,所有用户的提交都提交到该服务器中。
- 增量保存每次提交的Diff,如果提交的增量中和远端现存的文件存在冲突,则需要本地提前解决冲突。
优点:
- 学习简单,更容易操作
- 支持二进制文件,对大文件支持更友好(如游戏美术素材)
缺点:
- 本地不存储版本管理的概念,所有提交都只能联上服务器后才可以提交
- 分支上的支持不够好,对于大型项目团队合作比较困难
- 用户本地不保存所有版本的代码,如果服务器故障容易导致历史版本的丢失
1.1.3分布式版本控制
代表性工具Git
基本原理:
- 每个库都存有完整的提交历史,可以直接在本地进行代码提交
- 每次提交记录的都是完整的文件快照,而不是记录增量
- 通过Push等操作来完成和远端代码的同步
优点:
- 分布式开发,每个库都是完整的提交历史,支持本地提交,强调个体
- 分支管理功能强大,方便团队合作,多人协同开发
- 校验和机制保证完整性,一般只添加数据,很少执行删除操作,不容易导致代码丢失
缺点:
- 相对SVN更复杂,学习成本更高
- 对于大文件的支持不是特别好(git-lfs工具可以弥补这个功能)
1.2 Git发展历史
Linux团队研发
Github 全球最大的代码托管平台,大部分的开源项目都放在这个平台上。
Gitlab 全球最大的开源代码托管平台。项目的所有代码都是开源的,便于在自己的服务器上完成gitlab的搭建。
Gerrit 由Google开发的一个代码托管平台,Android这个开源项目就托管在Gerrit之上。
02.Git的基本使用方式
Git基本命令
2.1 Git目录介绍
项目初始化
mkdir study
cd study
git init
其他参数
--initial-branch 初始化的分支
--bare 创建一个裸仓库(纯Git目录,没有工作目录)
--template 可以通过模板来创建预先构建好的自定义git目录
Git仓库
2.1.1 Git Config
不同级别的Git配置
--global
--system
--local
每个级别的配置可能会重复,但是高级别会覆盖低级别
2.2.2 常见Git配置
用户名配置
git config --global user.name "xxx"
git config --global user.email xxx@xxx.com
Instead of 配置
git config --global url.git@github.com:.insteadOf https://github.com/
Git命令别名配置
git config --global alias.cin "commit --amend --no-edit"
2.2 Git Remote
查看Remotegit remote -v
添加Remote
git remote add origin_ssh git@github.com:git/git.git
git remote add origin_http https://github.com/git/git.git
2.2.1 HTTP Remote
2.2.2 SSH Remote
URL: git@github.com:git/git.git
免密配置 ssh可以通过公私钥的机制,将生成公钥存放在服务端,从而实现免密访问
目前的Key的类型有四种,分别是dsa、rsa、ecdsa、ed25519。默认使用的是rsa,由于一些安全问题,现在已经不推荐使用dsa和rsa了,优先推荐使用ed25519
ssh-keygen -t ed25519 -C "your_email@example.com"
密钥默认存在 ~/.ssh/id_ed25519.pub
2.3 Git Add
administration@LAPTOP-V4GMFJON MINGW64 /e/桌面文件/demo (master)
$ touch readme.md
administration@LAPTOP-V4GMFJON MINGW64 /e/桌面文件/demo (master)
$ git add .
administration@LAPTOP-V4GMFJON MINGW64 /e/桌面文件/demo (master)
$ git status
On branch master
No commits yet
Changes to be committed:
(use "git rm --cached <file>..." to unstage)
new file: readme.md
administration@LAPTOP-V4GMFJON MINGW64 /e/桌面文件/demo (master)
$ tree .git
.git
|-- HEAD
|-- config
|-- description
|-- hooks
| |-- applypatch-msg.sample
| |-- commit-msg.sample
| |-- fsmonitor-watchman.sample
| |-- post-update.sample
| |-- pre-applypatch.sample
| |-- pre-commit.sample
| |-- pre-merge-commit.sample
| |-- pre-push.sample
| |-- pre-rebase.sample
| |-- pre-receive.sample
| |-- prepare-commit-msg.sample
| |-- push-to-checkout.sample
| `-- update.sample
|-- index
|-- info
| `-- exclude
|-- objects
| |-- 95
| | `-- d09f2b10159347eece71399a7e2e907ea3df4f
| |-- info
| `-- pack
`-- refs
|-- heads
`-- tags
9 directories, 19 files
administration@LAPTOP-V4GMFJON MINGW64 /e/桌面文件/demo (master)
$ git cat-file -p 95d09f2b10159347eece71399a7e2e907ea3df4f
hello world
2.4 Git Commit
administration@LAPTOP-V4GMFJON MINGW64 /e/桌面文件/demo (master)
$ git commit -m "add readme"
[master (root-commit) 610faa0] add readme
1 file changed, 1 insertion(+)
create mode 100644 readme.md
administration@LAPTOP-V4GMFJON MINGW64 /e/桌面文件/demo (master)
$ tree .git
.git
|-- COMMIT_EDITMSG
|-- HEAD
|-- config
|-- description
|-- hooks
| |-- applypatch-msg.sample
| |-- commit-msg.sample
| |-- fsmonitor-watchman.sample
| |-- post-update.sample
| |-- pre-applypatch.sample
| |-- pre-commit.sample
| |-- pre-merge-commit.sample
| |-- pre-push.sample
| |-- pre-rebase.sample
| |-- pre-receive.sample
| |-- prepare-commit-msg.sample
| |-- push-to-checkout.sample
| `-- update.sample
|-- index
|-- info
| `-- exclude
|-- logs
| |-- HEAD
| `-- refs
| `-- heads
| `-- master
|-- objects
| |-- 61
| | `-- 0faa0c939bb5e638b62272b17143a4bfc74f22
| |-- 95
| | `-- d09f2b10159347eece71399a7e2e907ea3df4f
| |-- 9a
| | `-- a4816ed4d7cce80d7fa619412de2aebededcee
| |-- info
| `-- pack
`-- refs
|-- heads
| `-- master
`-- tags
14 directories, 25 files
administration@LAPTOP-V4GMFJON MINGW64 /e/桌面文件/demo (master)
$ git cat-file -p 610faa0c939bb5e638b62272b17143a4bfc74f22
tree 9aa4816ed4d7cce80d7fa619412de2aebededcee
author Yuzi_Yang <3190102636@zju.edu.cn> 1653395821 +0800
committer Yuzi_Yang <3190102636@zju.edu.cn> 1653395821 +0800
add readme
administration@LAPTOP-V4GMFJON MINGW64 /e/桌面文件/demo (master)
$ git cat-file -p 9aa4816ed4d7cce80d7fa619412de2aebededcee
100644 blob 95d09f2b10159347eece71399a7e2e907ea3df4f readme.md
administration@LAPTOP-V4GMFJON MINGW64 /e/桌面文件/demo (master)
$ git log
commit 610faa0c939bb5e638b62272b17143a4bfc74f22 (HEAD -> master)
Author: Yuzi_Yang <3190102636@zju.edu.cn>
Date: Tue May 24 20:37:01 2022 +0800
add readme
2.5 Objects
commit/tree/blob在git里面都统一称为Object,除此之外还有个tag的object.
Blob 存储文件的内容
Tree 存储文件的目录信息
Commit 存储提交信息,一个Commit可以对应唯一版本的代码
如何把这三个信息串联在一起呢?
- 通过Commit寻找到Tree信息,每个Commit都会存储对应的Tree ID。
- 通过Tree存储的信息,获取到对应的目录树信息。
- 从tree中获得blob的ID,通过Blob ID获取对应的文件内容。
2.6 Refs
cat .git/refs/heads/master
Refs文件存储的内容:
administration@LAPTOP-V4GMFJON MINGW64 /e/桌面文件/demo (master)
$ cat .git/refs/heads/master
610faa0c939bb5e638b62272b17143a4bfc74f22
administration@LAPTOP-V4GMFJON MINGW64 /e/桌面文件/demo (test)
$ cat .git/refs/heads/test
610faa0c939bb5e638b62272b17143a4bfc74f22
refs的内容就是对应的Commit ID 因此把ref当做指针,指向对应的Commit来表示当前Ref对应的版本。
不同种类的ref refs/heads前缀表示的是分支,除此之外还有其他种类的ref,比如refs/tags前缀表示的是标签。
Branch
git checkout -b可以创建一个新分支
分支一般用于开发阶段,是可以不断添加Commit进行迭代的。
Tag
标签一般表示的是一个稳定版本,指向的Commit一般不会变更。git tag命令生成tag
2.7 Annotation Tag
附注标签,一种特殊的Tag,可以给Tag提供一些额外的信息。
如何创建附注标签?
通过git tag -a命令来完成附注标签的创建。
administration@LAPTOP-V4GMFJON MINGW64 /e/桌面文件/demo (test)
$ git tag -a v0.0.2 -m "add feature 1"
administration@LAPTOP-V4GMFJON MINGW64 /e/桌面文件/demo (test)
$ cat .git/refs/tags/v0.0.2
f3965f5a388b8ed0432ccb12f856ac75f7f4fc7c
查看该tag object的内容:
administration@LAPTOP-V4GMFJON MINGW64 /e/桌面文件/demo (test)
$ git cat-file -p f3965f5a388b8ed0432ccb12f856ac75f7f4fc7c
object 610faa0c939bb5e638b62272b17143a4bfc74f22
type commit
tag v0.0.2
tagger Yuzi_Yang <3190102636@zju.edu.cn> 1653397197 +0800
add feature 1
2.8 追溯历史版本
- 获取当前版本代码
通过Ref指向的Commit可以获取唯一的代码版本。
- 获取历史版本代码
Commit里面会存有parent commit字段,通过commit的串联获取历史版本代码。
- 修改文件,并提交,创建新的commit。
- 查看最新的commit,新增了parent信息。
2.9 修改历史版本
1.commit --amend
通过这个命令可以修改最近的一次commit信息,修改之后commit id会变
2.rebase
通过git rebase -i HEAD~3 可以实现对最近三个commit的修改:
1)合并commit 2)修改具体的commit message 3)删除某个commit
3.filter --branch
该命令可以指定删除所有提交中的某个文件或者全局修改邮箱地址等操作
2.10 Objects
新增的Object
修改commit后我们可以发现git object又出现了变化
新增commit object 7f 但是之前的 commit object 63 并没有被删除
悬空的Object
顾名思义就是没有ref指向的object。git fsck --lost-found命令可以查询悬空的object。
2.11 Git GC
GC
通过git gc命令,可以删除一些不需要的object,以及会对object进行一些打包压缩来减少仓库的体积。
Reflog
reflog是用于记录操作日志,防止误操作后数据丢失,通过reflog来找到丢失的数据,手动将日志设置为过期。
指定时间
git gc prune=now指定的是修剪多久之前的对象,默认是两周前。
2.12 完整的Git视图
2.13 Git Clone & Pull & Fetch
Clone
拉取完整的仓库到本地目录,可以指定分支,深度
Fetch
将远端某些分支最新代码拉取到本地,不会执行merge操作,会修改refs/remote内的分支信息,如果需要和本地代码合并需要手动操作。
Pull
拉取远端分支,并和本地代码进行合并,操作等同于git fetch + git merge,也可以通过git pull --rebase完成git fetch + git rebase操作。
可能存在冲突,需要解决冲突。
2.14 Git Push
常用命令
一般使用git push origin master命令即可完成
冲突问题
- 如果本地的commit记录和远端的commit历史不一致,则会产生冲突,比如git commit --amend or git rebase都有可能导致这个问题。
- 如果该分支就自己一个人使用,或者团队内确认可以修改历史则可以通过
git push origin master -f来完成强制推送,一般不推荐主干分支进行该操作,正常都应该解决冲突后再进行推送。
推送规则限制
可以通过保护分支,来配置一些保护规则,防止误操作,或者一些不合规的操作出现,导致代码丢失。(Github可以进行配置)
03.Git研发流程
3.1 不同的工作流
3.2 集中式工作流
3.2.1 集中式工作流-Gerrit
3.3分支管理工作流
3.3.1 分支管理工作流-GitFlow
3.3.2 分支管理工作流-Github Flow
3.3.3 分支管理工作流-Gitlab Flow
3.4 代码合并
Fast-Forward
不会产生一个merge节点,合并后保持一个线性历史,如果target分支有了更新,则需要通过rebase操作更新source branch后才可以合入。
Three-Way Merge
三方合并,会产生一个新的merge节点