这是我参与「第三届青训营 -后端场」笔记创作活动的的第3篇笔记。
引言
为什么要学习 Git
协同开发
业界绝大多数公司都是基于 Git 进行代码管理,因此 Git 是一个程序员的必备技能。
开源社区
目前绝大多数的开源项目都是基于 Git 维护的,参与这些项目的开发都需要使用 Git 。
目标
- 学习基本的 Git 命令,并且了解原理,在遇到 Git 相关问题时,能自行排查并解决。
- 了解研发流程中的基本概念和规范,学会正确的使用 Git 。
目录
1. Git 是什么
- 介绍版本控制的发展历史,为什么会出现 Git 。
- 介绍 Git 的发展历史
2. Git 基本使用方法
- Git 的基本命令介绍,如何使用这些命令,以及命令的原理。
3. Git 研发流程
- 依托代码管理平台 Gitlab / Github / Gerrit 介绍我们如何进行代码的开发和团队协作。
Git 是什么
1. 版本控制
- 版本控制是什么? 一种记录一个或若干文件内容的变化,以便将来查阅特定版本修订情况的系统
- 为什么需要版本控制? 更好的关注变更,了解到每个版本的改动是什么,方便对改动的代码进行检查,预防事故发生;也能够随时切换到不同的版本,回滚误删误改的问题代码。
版本控制类型
- 本地版本控制 :RCS
- 集中式的版本控制 :SVN
- 分布式的版本控制 :Git
1.1 本地版本控制
最初的方式,通过复制本地文件夹,来完成版本控制,通过不同的文件名来区分版本。
开发了一些本地的版本控制软件,其中最流行的就是RCS
本地保存的所有变更的补丁集,可以理解成就是所有的 Diff ,通过这些补丁,我们可以计算出每个版本的实际的文件内容
缺点 :只能在本地使用,无法进行团队协作做。
1.2 集中式版本控制
代表工具 :SVN
基本原理 :
- 提供一个远端服务来保存文件,所有用户的提交都提交到该服务器中。
- 增量保存每次提交的 Diff ,如果提交的增量中和远端现存的文件存在冲突,则需要本地提前解决冲突。
优点 :
- 学习简单,容易操作
- 支持二进制文件,对大文件支持更加友好。现在一些美术团队、游戏团队可能会使用。
缺点 :
- 本地不储存版本管理的概念,所有提交都只能连上服务器后才可以提交。
- 分支上的支持不够好,对于大型项目团队合作比较困难。
- 用户本地不保存所有版本的代码,如果服务器端故障容易导致历史版本的丢失。
1.3 分布式版本控制
代表工具 :Git 基本原理 :
- 每个库都存有完整的提交历史,可以直接在本地进行代码提交。
- 每次提交记录的都是完整的快照,而不是记录增量。
- 通过 Push 等操作来完成和远端代码的同步。
优点 :
- 分布式开发,每个库都是一个完整的提交历史,支持本地提交,强调个体。对于开源来说非常好。
- 分支管理功能强大,方便团队合作,多人协同开发。
- 校验和机制保证完整性,一般只添加数据,很少执行删除操作,不容易导致代码丢失。
缺点 :
- 复杂,学习成本高。
- 对于大文件支持不好,一般不要在 Git 提交大文件 ( git-lfs 工具可以弥补这个功能)。
2. Git 发展历史
Github :全球最大的代码托管平台。
Gitlab :全球最大的开源代码托管平台。
Gerrit :由谷歌开发的代码托管平台,对安卓这种多仓的项目支持比较好。
Git 基本使用方法
Git 基本命令
-
配置 :git config 、 git remote
-
提交代码 :git add 、git commit
-
远端同步 :push 、clone 、pull 、fetch
常见问题
- 为什么配置了 Git 配置,依然没办法拉取代码?
- 没有配置密钥。
- Instead Of 配置没有配,配的 SSH 免密配置,但是使用的还是 HTTP 协议访问。
- 为什么 Fetch 了远端分支,但是看本地当前的分支历史还是没有变化?
- Fetch 会把代码拉取到本地的远端分支,但是并不会合并到当前分支,所以当前分支历史没有变化。
1. Git 目录介绍
项目初始化
git init 默认是创建 master 分支
--initial-branch 初始化的分支
--bare 创建一个裸仓库。一般存在服务器上的仓库是这样创建的,本地的就是不加参数的创建
--template 可以通过模板来创建预先构建好的自定义 git 目录
1.1 Git Config
不同级别的配置 :global 、 system 、local。
低级覆盖高级
1.2 常见配置
用户名配置
Instead of 配置
Git 命令别名配置
2. Git Remote
查看 :git remote -v
添加 :
git remote add origin_ssh git@github.com:git/git.gitgit remote add origin_http https:github.com/git/git.git
同一个 Origin 设置不同的 Push 和 Fetch URL
git remote set-url --add --push origin git@github.com:mymymym/git.git
2.1 HTTP Remote
免密配置
内存 :git config --global credential.helper 'cache --timeout=3600'
硬盘 :git config --global credential.helper "store --file /path/to/credential-file"
指定文件,具体格式 :${scheme}://${user}:${password}@github.com
HTTP 不推荐。没那么安全,而且不方便。
2.2 SSH Remote
免密配置:
默认使用的是rsa , 但是现在已经不推荐使用,优先使用 ed25519。
3. Git Add
创建文件,add之后放入暂存区
4. Git Commit
Commit 提交。
git cat-file -p 可以查看加密的文件内容。
5. Objects
三种 Objects
Blob :存储文件内容
Tre :存储文件目录信息
Commit :存储提交信息,一个 Commit 可以对于唯一版本的代码
三种信息串联在一起 :
- 通过 Commit 寻找 Tree ,每个 Commit 都会存储对应的 Tree ID
- 通过 Tree 存储的信息,获取对应的目录树的信息
- 从 Tree 中获取 Blob ID,通过 Blob ID 获取对应的文件内容
6. Refs
Refs 文件存储内容
refs 的内容就是 对应的 Commit ID
不同种类的 ref
heads 前缀表示的是分支、tags 前缀表示的是标签。
Branch :git checkout -b 可以创建一个新分支,分支一般用于开发阶段,是可以不断添加 Commit 进行迭代的
Tag :标签一般表示的是一个稳定的版本,指向的 Commit 一般不会变更
通过 git tag 命令生成 tag
7. Annotation Tag
附注标签 :一种特殊的 Tag,可以给 Tag 提供一些额外的信息。
通过 git tag -a 命令来创建。
如下,里面存储的内容和 tag 不一样了。将其打开可以看到里面的内容:
object 指向的是和之前一样的 Commit ,最后的是我们添加的内容。
8. 追溯历史版本
-
获取当前版本代码 :通过 Ref 指向的 Commit 可以获取
-
获取历史版本代码 :Commit 里面会存有 parent commit 字段,通过 Commit 串联获取历史版本代码
提交新版本会新增三个 Objects tree / blob / commit。ref指向新的commit。
9. 修改历史版本
-
commit --amend:修改最近一次 commit 信息,修改之后 commit id 会变 -
rebase :
通过
git rebase -i HEAD~3可以实现对最近三个 commit 的修改:1. 合并 commit 2. 修改具体的 commit message 3. 删除某个 commit -
filter --branch:指定删除所有提交中的某个文件或者全局修改邮箱地址等操作
通过 git commit --amend命令,尝试修改一下 commit message
通过 git log 查看 commit 已经变了。
10. Objects
修改 Commit 之后,老的 object 没有被删除。
悬空的 Object :没有 ref 指向的 Object
git fsck --lost-found 找出悬空 Object :
11. Git GC
通过 git gc 命令,可以删除一些不需要的 Object
以及会对 object 进行一些打包压缩来减少仓库的体积。
reflog 用于记录操作日志,防止误操作后数据丢失,通过 reflog 来找到丢失的数据,手动将日志设置为过期来删除不需要的 Object
git gc prune=now 指定的是修剪多久之前的对象,默认是两周前。
如下图,操作之后发现之前找出来的悬空 Object 已经不存在了。以及还将之前的 objects 和 refs 打包了。
12. 完整的 Git 视图
13. Git Clone & Pull & Fetch
Clone :拉取完整的仓库到本地目录,可以指定分支,深度。
Fetch :将远端某些分支最新代码拉取到本地,不会执行 merge 操作,会修改 refs / remote 内的分支信息,如果需要和本地代码合并需要手动操作。
Pull :拉取远端某分支,并且和本地代码进行合并,操作等同于 git fetch + git merge,也可以通过 git pull --rebase 完成 git fetch + git rebase 操作。可能存在冲突,需要解决冲突。
一般对代码很清楚很了解,推荐 Pull。如果不清楚远端是什么状态会不会存在冲突,用 Fetch 。
14. Git Push
- 常见命令:
一般使用
git push origin master命令即可完成 - 冲突问题:
- 如果本地的 commit 记录和远端不一样,就会产生冲突,
git comit --amend or git rebase都有可能导致这个问题。 - 如果该分支就自己一个人使用,或者团队确认过可以修改历史则可以通过
git push origin master -f来强制完成推送,一般不推荐在主干分支进行该操作,正常都是在解决冲突后再进行推送。
- 如果本地的 commit 记录和远端不一样,就会产生冲突,
- 推送规则限制: 可以通过保护分支,来配置一些规则,防止误操作,或者是一些不合规的操作出现,导致代码丢失。
Git 研发流程
在 GitHub 上怎么进行团队协作。
常见问题
- 在 Gerrit 平台上使用 Merge 的方式合入代码
- 不了解保护分支, Code Review ,CI 等概念,研发流程不规范
- 代码历史混乱,代码合并方式不清晰
1. 不同的工作流
集中式工作流 :Gerrit 、 SVN
分支管理工作流 :Github 、 Gitlab
2. Git Flow
-
包含五种分支:
- Master:主干分支
- Develop:开发分支
- Feature:特性分支
- Release:发布分支
- Hotfix:热修复分支
-
优点: 如果能按照定义的标准严格执行,代码会很清晰,并且很难出现混乱。
-
缺点: 流程过于复杂,上线的节奏会比较慢。由于太复杂,研发容易不按照标准执行,从而导致代码出现混乱。
3. Github Flow
Github 的工作流,只有一个主管那分支,基于 Pull Request 往主干分支中提交代码。
团队合作方式:
- owner 创建好仓库后,其他用户通过 Fork 的方式来创建自己的仓库,并且在 fork 的仓库是进行开发
- owner 创建好仓库后,统一给团队内成员分配权限,直接在同一个仓库内进行开发
第二种演示
创建一个 Pull Request
- 创建 main 分支 :GitHub 上可以看到
- 创建 feature 分支 :GitHub上提示创建了新的分支
- 检查之后合入 main 分支
可以添加一些规则
4. Gitlab Flow
upstream first 上游优先
5. 代码合并
Fast-Forward :不会产生 merge 结点,如下:与之前对比没有 Merge 结点
Three-Way Merge :三方合入,会产生 merge 结点