『字节青训营-3rd』L11:Git 的正确使用姿势与最佳实践
Git 是什么
版本控制
版本控制是什么?
一种记录一个或若干文件内容变化,以便将来查阅特定版本修订情况的系统
本地版本控制
- 代表:RCS
- 依托于本地磁盘进行版本控制
集中式版本控制
- 代表:SVN
- 存在一个统一的远端服务器,用于版本控制,本地不存储版本控制
分布式版本控制
- 代表:Git
- 每个库都拥有所有的版本控制信息,远端服务器用于不同库之间进行版本信息同步
Git 发展历史
最初版由 Liunx 创始人 Linus Torvalds 花两周时间开发而成,主要是为了用于 Linux 项目的维护
Git 基本使用方式
Git 目录介绍
项目初始化
1 | mkdir demo |
其他参数
--initial-branch
初始化的分支--bare
创建裸仓库(纯 Git 目录,没用工作目录)--template
通过模板构建自定义 git 目录
观察 .git
目录
-
HEAD:当前的分支
-
config:配置
-
objects:文件信息
-
refs:分支信息
hooks 里面的一堆 .sample
文件是例子,不会执行
工作区和暂存区
Git Config
不同级别的 Git 配置:全局、系统、当前
低级别可以覆盖高级别
常见的 Git 配置
-
用户名配置
1
2git config --global user.name "xxx"
git config --global user.email [email protected] -
Instead of 配置
相当于替换
1
git config --global url [email protected]:.insseadOf https://github.com/
-
Git 命令别名配置
1
git config --global alias.cin "commit --amend --no-edit"
Git Remote
-
查看
1
git remote -v
-
添加
1
2git remote add origin_ssh [email protected]:git/git.git
git remote add origin_http https://github.com/git/git.git
这时再看 config,就会发现多了两个配置
可以同一个 Origin 设置不同的 Push 和 Fetch URL
1 | git remote add origin [email protected]:git/git.git |
HTTP Remote
出于安全性,一般不使用这种方式
URL: https://github.com/git/git.git
免密配置:
SSH Remote
URL: [email protected]:git/git.git
免密配置
生成并查看公钥,之后存到 GitHub 上即可
可以创建多对公私钥
Git Add
将文件添加至暂存区
1 | touch readme.md |
之后查看 .git
目录,可以看见多两个文件
这个文件的内容实际上就是我们修改的内容
Git Commit
把修改从暂存区提交到储存库中
1 | git commit -m "add readme" |
检查 .git
目录,发现多了两个文件,分别存储 blob 类型和 tree 类型
Objects
commit、tree 和 blob 在 git 里统一称为 Object,除此之外还有一个 Object,在下文会提到
Blob
存储文件的内容
Tree
存储文件目录信息
Commit
存储提交信息,一个 Commit 可以对应唯一版本的代码
如何把这三个信息串联在一起?
- 通过 Commit 寻找 Tree
- 通过 Tree 寻找若干 Blob
- 通过若干 Blob 获取对应文件内容
Refs
Refs 中存储的内容
refs 的内容就是对应的 Commit ID
因此把 ref 当作指针,指向对应的 Commit 来表示当前对应的版本
Branch 和 Tag
-
Branch
分支一般用于开发阶段,可以不断添加 Commit 来迭代
-
Tag
标签一般表示一个稳定版本,指向的 Commit 一般不会变更
新建分支
1 | git checkout -b test |
创建好后,可以在 refs 中看见
新建标签
1 | git tag v0.0.1 |
Annotation Tag
附注标签:一种特殊的 Tag,可以提供一些额外的信息
1 | git tag -a v0.0.2 -m "add feature 1" |
创建之后,发现 objects 中也新建了一个文件,而这个附注标签就是指向这个文件的
这个文件就属于上文中的第四种 object —— Tag Object,指向的是一个 commit,内容是一些附加信息
追溯历史版本
-
获取当前版本代码
通过 Ref 指向的 Commit 可以获取唯一的代码版本
-
获取历史版本代码
现在修改一下 readme.md ,在开头加一个井号,之后提交
查看
object
文件夹,又多了三个文件通过
git log
,找到这次提交的编号检查这次提交,发现多了一个 parent 值,这就关联到了历史版本
修改历史版本
-
commit --amend
修改最近的一次 commit 信息,修改后 commit id 会变
-
rebase
通过
git rebase -i HEAD~3
可以实现对最近三个 commit 的修改- 合并 commit
- 修改具体的 commit message
- 删除某个 commit
-
filter --branch
可以指定删除所有提交中的某个文件或者全局修改邮件地址等操作
这里尝试一下第一个命令,执行后会打开文本编辑窗口让你修改
1 | git commit --amend |
可以看见 commit id 已经改变了
而老的 commit 其实就已经没有 ref 去指向了,这时就变成了一个悬空的 object,可以使用下面的命令来查找
1 | git fsck --lost-found |
Git GC
GC
删除一些不需要的 object,已经会对 object 进行一些打包压缩来减少仓库体积
使用 --prune
参数指定修剪多久之前的对象,默认是两周
Reflog
记录操作日志,防止误操作后数据丢失,
尝试以下命令
1 | git reflog expire --expire=now --all |
然后观察目录变化,发现变化很大
完整的 Git 视图
Git Clone & Pull & Fetch
Clone
拉取完整的仓库到本地,可以指定分支、深度
Fetch
将远程某些分支最新代码拉取到本地,不会 merge
会修改 refs/remote 内的分支信息,如果需要和本地代码合并需要手动操作
Pull
拉取并合并,相当于 git fetch
+ git merge
也可以通过 git pull --rebase
完成 git fetch +get rebase
操作
Git Push
常用命令
一般使用 git push orgin master
即可
冲突问题
- 如果本地的 commit 记录和远端的 commit 记录历史不一致,则会产生冲突
- 如果该分支就自己一个人使用,或团队内确认可以修改历史则可以通过
-f
参数强制推送
推送限制规则
可以通过保护分支,来配置一些保护规则
Git 研发流程
不同的工作流
-
集中式工作流
代表:SVN,Gerrit
只依托于主干分支进行开发,不存在其他分支
-
分支管理工作流
代表GitHub,Gitlab
可以定义不同特性的开发分支,上线分支,在分支开发完成后在通过 MR/PR 合入主分支
集中式工作流
Gerrit
分支管理工作流
工作流 | 特点 |
---|---|
Git Flow | 分支类型丰富,规范严格 |
GitHub Flow | 只有主干和分发分支,规则简单 |
GitLab Flow | 在主干分支和开发分支之上构建环境分支,版本分支,满足不同发布和环境的需要 |
Git Flow
GitHub Flow
只有一个主干,基于 Pull Request 往主干分支提交代码
GitLab Flow
GitLab 推荐的工作流是上面两者的优化
代码合并
Fast-Forward
1 | git merge test --ff-only |

Tree-Way Merge
1 | git marge test --no-ff |

如何选择合适的工作流
没有最好的,只有最合适的