这是我参与「第五届青训营 」伴学笔记创作活动的第 6 天
Git
版本控制
- 一种记录一个或若干文件内容变化,以便将来查询特定版本修订情况的系统
- 更好的关注变更,了解到每个版本的变动是什么,方便对改动的代码进行检查,预防事故的发生,也能够随时切换到不同的版本,,回滚误删误改的问题代码
| 版本控制类型 | 代表性工具 | 解决的问题 |
|---|---|---|
| 本地版本控制 | RCS | 本地代码的版本控制 |
| 集中式版本控制 | SVN | 提供一个远端服务器来维护代码版本,本地不保存代码版本;解决多人协作问题 |
| 分布式版本控制 | Git | 每个仓库都能够记录版本历史,解决只有一个服务器保存版本的问题 |
本地版本控制
-
最初的方式
- 通过本地复制文件夹,来完成版本控制,一般可以通过不同的文件名来区分版本
-
解决方案
- 开发了一些本地的版本控制软件,其中最流行的式 RCS
-
基本原理
- 本地保存所有变更的补丁集
-
缺点
- RCS 这种本地版本控制存在最致命的缺陷就是只能在本地使用,无法进行团队协作,因此使用的场景非常有限,因此衍生出了集中式版本控制
集中版本控制
-
代表性工具: SVN
-
基本原理
- 提供一个远程服务来保存文件,所有用户的提交都提交到该服务器中
- 增量保存每次提交的 Diff ,如果提交的增量中和远端现存的文件存在冲突,则需要本地提前解决冲突
-
优点
- 学习简单,容易操作
- 支持二进制文件,对大文件支持更友好
-
缺陷
- 本地不存储版本管理的概念,所有提交都只能连上服务器后才可以提交
- 分支上的支持不够友好,对于大型项目团队合作比较困难
- 用户本地不保存所有版本的代码,如果服务端故障容易导致历史版本的丢失
分布式版本控制
-
代表性工具:Git
-
基本原理
- 每个库都存有完成的提交历史,可以直接在本地进行代码提交
- 每次提交记录的都是完整的文件快照,而不是记录增量
- 通过 Push 等操作来完成和远端代码的同步
-
优点
- 分布式开发,每个库都是完整的提交历史,支持本地提交,强调个体
- 分支管理功能强大,方便团队合作,多人协同开发
- 校验和机制保证完整性,一般只添加数据,很少执行删除操作,不容易导致代码丢失
-
缺点
-
相对 SVN 更复杂,学习成本高
-
对于大文件的支持不是很友好
- git-lfs工具可以弥补
-
Git 的基本使用方式
Git 基本命令
-
配置
-
git config
- 用户名配置
- Instead of 配置
- Git 命令别名配置
-
git remote
- 查看 remote
- 添加 remote
- 同一个 Origin 设置不同的 Push 和 Fetch URL
- HTTP remote
- SSH remote
-
-
提交代码
- git add
- git commit
-
远程同步
-
拉取代码
- clone
- pull
- fetch
-
推送代码
- push
-
-
Objects
commit / tree / blob 在 git 里面统一称为 Object,除此之外还有个 tag 的object
- Blob
存储文件的内容
- Tree
存储文件的目录信息
- Commit
存储提交信息,一个Commit可以对应唯一版本的代码
-
将三个信息串联在一起
- 通过 Commit 寻找到 Tree 信息,每个 Commit 都会存储对应的 Tree ID
- 通过 Tree 存储的信息,获取到对应的目录树信息
- 从 tree 中获得 blob 的 ID,通过 Blob ID 获取对应的文件内容
-
Refs
-
Refs 文件存储的内容
- refs 的内容就是对应的 Commit ID
- 因此把 ref 当作 指针,指向对象的 Commit 来表示当前 Ref 对应的版本
-
不同种类的 ref
- refs / heads 前缀表示的是分支,除此之外还有其他种类的 ref,比如 refs / tags 前缀表示的是标签
-
Branch
- git checkout -b 可以创建一个新分支
- 分支一般用于开发阶段,是可以不断添加 Commit 进行迭代的
-
Tag
- 标签一般表示的是一个稳定版本,指向的 Commit 一般不会变更
- 通过 git tag 命令生成 tag
-
-
Annotation Tag
-
一种特殊的 Tag,可以给 Tag 提供一些额外的信息
-
创建附注标签
- 通过 git tag -a 命令来完成附注标签的创建
- 查看该 tag object 的内容
-
-
追溯历史版本
-
获取当前版本代码
- 通过 Ref 指向的 Commit 可以获取唯一的代码版本
-
获取历史版本代码
- Commit 里面会存有 parent commit 字段,通过 commit 的串联获取历史版本代码
-
-
修改历史版本
-
commit - amend
- 通过这里命令可以修改最近一次 commit 信息,修改之后commit id 会改变
-
rebase
-
通过 git rebase -i HEAD~3 可以实现对最近三个 commit 的修改
- 合并 commit
- 修改具体的 commit message
- 删除某个 commit
-
-
filter - branch
- 该命令可以指定删除所有提交中的某个文件或者全局修改邮箱地址等操作
-
-
Git GC
-
GC
- 通过 git gc 命令,可以删除一些不需要的 object,以及会对 object 进行一些打包压缩来减少仓库的体积
-
Reflog
- reflog 是用于记录操作日志,防止误操作后数据丢失,通过 reflog 来找到丢失的数据,手动将日志设置为过期
-
指定时间
- git gc prune=now 指定的是修剪多久之前的对象,默认是两周前
-
-
Git Clone & Pull & Fetch
-
Clone
- 拉取完整的仓库到本地目录,可以指定分支,深度
-
Fetch
- 将远端某些分支最新代码拉取到本地,不会执行 merge 操作,会修改 refs / remote 内的分支信息,如果需要和本地代码合并需要手动操作
-
Pull
- 拉取远端某分支,并和本地代码进行合并,操作等同于 git fetch + git merge,也可以通过 git pull -- rebase 完成 git fetch + git rebase 操作
- 可能存在冲突,需要解决冲突
-
-
Git Push
-
常用命令
- 一般使用 git push origin master 命令即可完成
-
冲突问题
- 如果本地的 commit 记录和远端的 commit 历史不一致,则会产生冲突,比如 git commit --amend or git rebase 都有可能导致这个问题
- 如果该分支就自己一个人使用,或者团队内确认可以修改历史则可以通过 git push origin master -f 来完成强制推送,一般不推荐主干分支进行该操作,正常都应该解决冲突后再进行推送
-
推送规则限制
- 可以通过保护分支,来配置一些保护操作,防止误操作,或者一些不合规的操作出现,导致代码丢失
-
Git 研发流程
- 不同的工作流
| 类型 | 代表平台 | 特点 | 合入方式 |
|---|---|---|---|
| 集中式工作流 | Gerrit / SVN | 只依托于主干分支进行开发,不存在其他分支 | Fast-forward |
| 分支管理工作流 | GitHub / GitLab | 可以定义不同特性的开发分支,上线分支,再开发分支完成开发后再通过 MR / PR 合入主干分支 | 自定义,Fast-Forward or Three-Way Merge 都可以 |
-
集中式工作流
-
只依托于 master 分支进行研发活动
-
工作方式
- 获取远端 master 代码
- 直接在 master 分支完成修改
- 提交前拉取最新的 master 代码和本地代码进行合并(使用 rebase),如果有冲突需要解决冲突
- 提交本地代码到 master
-
集中式工作流-Gerrit
-
基本原理
- 依托于 Change ID 概念,每个提交生成一个单独的代码评审
- 提交上去的代码不会存储在真正的 refs / heads / 下的分支中,而是存在一个 refs / for 的引用下
- 通过 refs / meta / config 下的文件存储代码的配置,包括权限,评审等配置,每个 Change 都必须要完成 Review 后才能合入
-
优点
- 提供强制的代码评审机制,保证代码的质量
- 提供更丰富的权限功能,可以针对分支做细粒度的权限管控
- 保证 master 的历史整洁性
- Aosp 多仓的场景支持共呢个好
-
缺点
- 开发人员较多的情况下,更容易出现冲突
- 对于多分支的支持较差,想要区分多个版本的线上代码时,更容易出现问题
- 一般只有管理员才能创建仓库,比较难以在项目之间复用,不如类似的 fork 操作就不支持
-
-
-
分支管理工作流
| 分支管理工作流 | 特点 |
|---|---|
| Git Flow | 分支类型丰富,规范严格 |
| GitHub Flow | 只有主干分支和开发分支,规则简单 |
| GitLab Flow | 在主干分支和开发分支之上构建环境分支,版本分支,满足不同发布 or 环境的需要 |
-
分支管理工作流 GitFlow
-
比较早期出席那 的分支管理策略
-
包括五种类型的分支
- Master:主干分支
- Develop:开发分支
- Feature:特性分支
- Release:发布分支
- Hotfix:热修复分支
-
优点
- 如果能按照定义的标准严格执行,代码会很清晰,并且很难出现混乱
-
缺点
- 流程过于复杂,上线的节奏会比较慢,由于太复杂,研发容易不按照标准执行,从而导致代码出现混乱
-
-
分支管理工作流-Github Flow
-
GitHub 的工作流,只有一个主干分支,基于 Pull Request 往主干分支中提交代码
-
选择团队合作的方式
- owner 创建好仓库后,其他用户通过 Fork 的方式来创建自己的仓库,并在 fork 的仓库上进行开发
- owner 创建好仓库后,统一给团队内成员分配权限,直接在同一个仓库内进行开发
-
创建一个 Pull Request
- 创建一个 main 主分支
- 创建一个 feature 分支
- 创建一个feature 到 main 的 Pull Request
- 可以在 Pull Request 页面执行 CI / CA / CR 等操作,都检查通过后,执行合入
-
-
分支管理工作流-GitLab Flow
-
Gitlab 推荐的工作流时在 GitFlow 和 Github Flow 上做出优化,既保持了单一主分支的简便,又可以适应不同的开发环境
-
原则
- upstream first 上游优先
只有在上游分支采纳的代码才可以进入到下游分支,一般上游分支就是master
-
-
代码合并
-
Fast-Forward
- 不会变成一个 merge 节点,合并后保持一个线性历史,如果 target 分支有了更新,则需要通过 rebase 操作更新 source branch 后才可以合入
-
Three-Way Merge
- 三方合并,会产生一个新的 merge 节点
-
-
补充
-
在 Gerrit 平台上使用 Merge 的方式合入代码
- Gerrit 是集中式工作流,不推荐使用 Merge 方式合入代码,应该是主干分支开发后,直接 Push
- 保护分支:防止用户直接向主干分支提交代码,必须通过 PR 来进行合入
- Code Review,CI:都是在合入前的代码检查策略,Code Review 是人工检查,CI 则是通过一些定制化的脚本来进行一些校验
-