Git 的正确使用姿势与最佳实践 | 青训营笔记
这是我参与「第三届青训营 -后端场」笔记创作活动的的第11篇笔记
方向 | 具体能力 |
---|---|
代码托管 | 负责管理公司内数十万的代码仓库,并在这之上对代码管理的相关功能进行迭代,提升研发活动的效率及质量 |
代码智能 | 提供更准确高效的代码搜索能力和代码导航能力,支持多种场景下的代码跳转,帮助用户更高效的去阅读代码 |
代码分析 | 提供一种代码检查能力,目的是在整个研发流程中自动的发现并反馈代码中存在的代码结构、代码漏洞、代码风格等问题 |
持续集成 | 一种软件开发实践,团队成员频繁将他们的工作成果集成在一起。每次提交后,自动触发运行一次包含自动化验证集的构建任务,以便能尽早发现集成问题 |
Cloud IDE | 一个开箱即用的云端开发环境,支持node/ python/go/java/c++等多种编程语言。 |
一、引言
为什么要学习 Git ?
-
协同工作
- 业界绝大多数公司都是基于Git进行代码管理,因此Git是一个程序员的必备技能
-
开源社区
- 目前绝大多数的开源项目都是基于Git维护的,参与这些项目的开发都需要使用Git。
二、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. 版本控制
版本控制类型 | 代表性工具 | 解决的问题 |
---|---|---|
本地版本控制 | RCS | 依托于本地磁盘进行版本控制 |
集中式版本控制 | SVN | 存在一个统一的远端服务器,用于版本控制,本地不存储版本控制 |
分布式版本控制 | Git | 每个库都拥有所有的版本控制信息,远端服务器用于不同库之间进行版本信息同步 |
1.1 本地版本控制
最初的方式
通过本地复制文件夹,来完成版本控制,一般可以通过不同的文件名来区分版本
解决方案
开发了一些本地的版本控制软件,其中最流行的是RCS
基本原理
本地保存所有变更的补丁集,可以理解成就是所有的Diff,通过这些补丁,我们可以计算出每个版本的实际的文件内容
缺点
RCS这种本地版本控制存在最致命的缺陷就是只能在本地使用,无法进行团队协作,因此使用的场景非常有限,因 此衍生出了集中式版本控制
1.2 集中式版本控制
基本原理:
- 提供一个远端服务来保存文件, 所有用户的提交都提交到该服务器中
- 增量保存每次提交的Diff,如果提交的增量中和远端现存的文件存在冲突,则需要本地提前解决冲突
优点:
- 学习简单,更容易操作
- 支持二进制文件,对大文件支持更友好
缺点:
- 本地不存 储版本管理的概念,所有提交都只能联上服务器后才可以提交
- 分支上的支持不够好,对于大型项目团队合作比较困难
- 用户本地不保存所有版本的代码,如果服务端故障容易导致历史版本的丢失。
1.3 分布式版本控制
代表性工具: Git
基本原理:
- 每个库都存有完整的提交历史,可以直接在本地进行代码提交
- 每次提交记录的都是完整的文件快照,而不是记录增量
- 通过Push等操作来完成和远端代码的同步
优点:
- 分布式开发,每个库都是完整的提交历史,支持本地提交,强调个体
- 分支管理功能强大,方便团队合作,多人协同开发
- 校验和机制保证完整性,一 般只添加数据,很少执行删除操作,不容易导致代码丢失
缺点:
- 相对SVN更复杂,学习成本更高
- 对于大文件的支持不是特别好(git- _Ifs 工具可以弥补这个功能)
2. 发展历史
作者
最初版由 Liunx 创始人 Linus Torvalds 花两周时间开发而成,主要是为了用于 Linux 项目的维护
Linus Torvalds (就是Linux这个项目的作者,同时也是Git的作者)。
开发原因
怀疑Linux团队对BitKeeper (另-种分布式版本控制系统,专有软件)进行了逆向工程,BitKeeper 不允许Linux团队继续无偿使用。因此决定自己开发一个分布式版本控制系统。
衍生平台
- Github : 全球最大的代码托管平台,大部分的开源项目都放在这个平台上。 github.com/
- Gitlab : 全球最大的开源代码托管平台,项目的所有代码都是开源的,便于在自己的服务器上完成Gitlab的搭建。gitlab.com/gitlab-org
- Gerrit : 由Google开发的一个代码托管平台,Android 这个开源项目就托管在Gerrit之上。 android-review.googlesource.com/q/status:op…
- 除此之外,还有 bitbucket, Coding, 码云 等一系列平台
三、Git 命令基本使用方式和原理
3.1 目录
项目初始化
mkdir study
cd study
git init
git init
其他参数
--initial-branch
初始化的分支 -bare
创建一个裸仓库(纯Git目录,没有工作目录) --template
可以通过模版来创建预先构建好的自定义git目录
3.2 目录、文件操作
pwd
( print working directory ) 查看当前所在路径–绝对路径
cd
( change directory ) 切换目标
cd ..
返回到上一个目录
ls
( list ) 查看当前目录下的内容
ll
列出的内容更为详细ll 列出的内容更为详细
ls -al
包括隐藏文件和以 . 开头的文件
mkdir
( make directory ) 创建目录
touch
创建文件
cat
查看文件内容(一次性将内容全部显示)
less
查看文件内容(显示部分信息)–再次输入‘回车’一行一行显示,‘空格’一页一页显示 ,‘b’一次向上走一页
rm
( remove ) 删除文件,-rm -rf 文件夹(循环递进删除文件夹,不需要二次确认)
rmdir
( remove directory )删除文件夹(只能删除空文件夹,不常用)
clear
清屏
q
退出
mv
( move ) 移动文件或重命名
cp
( copy ) 复制文件 如复制 A 文件夹下的所有子文件、目录到 B 文件夹 cp -r A/ B*
echo "something" >> 文件名
把内容追加到某个文件
echo ‘内容’ > 文件名
(输出内容到文件中,每次输入都是覆盖原来的文件)
echo ‘内容’ >>文件名
(输出内容到文件中,每次输入都是追加新内容)
3.3 Git 配置
-
Git Config
Git 配置,分成本地,用户,系统基本的配置
-
用户名配置:
git config --global user.name "You Name"
(注意前边是“--global”,有两个横线)git config --global user.email "email@example.com"
-
Instead of 配置(ssh 协议 替换 http协议)
git congif --global url.git@github.com:.instandOf https://github.com/
-
git命令别名配置
git config -- global alias.cin "commit --amend --no-edit"
-
-
Git Remote
Git Remote 配置,分成 SSH 和 HTTP 两种协议实现,不同协议有不同的免密配置方式
- 查看 Remote :
git remote -v
- 添加 ssh :
git remote add origin_ssh git@github.com:git/git.git
- 添加 http :
git remote add origin_http https://github.com:git/git.git
- 查看 Remote :
-
HTTP Remote
-
URL:
httsp://github.com/git/git.git
-
免密配置:
-
内存:
git config --global credential.helper 'cache --timeout=3600'
-
硬盘:
git config --global credential.helper "store --file/path/to/credential-file"
不指定目录的情况默认是~/.git-credentials
-
将密钥信息存在指定文件中
具体格式:
${scheme}://${user}:${password}@github.com
-
-
-
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
- SSH可以通过公私钥的机制,将生成公钥存放在服务端,从而实现免密访问 目前的Key的类型四种,分别是
-
3.4 代码提交
Git Add
将代码从工作区提交到暂存区
Git Commit
将暂存区代码提交到 Git 存储
fetch和push不同仓库 : git remote set-url --add --push git@github.comXX/git/git
3.5 Git 存储基本概念
-
Ref 的内容就是对应的Commit ID
-
Tag 仓库标签: 标签一般是一个稳定的版本,指向的commit一般不会变更
-
创建标签
git tag v0.0.1
-
附注标签:一种特殊的tag,可一个tag提供一些额外信 息
git tag -a v0.0.2
-
-
Branch 仓库分支: 分之一般用于开发阶段,是可以不断添加commit进行迭代的
git checkout -b + 分支名
新建分支
因此把ref当做指针,指向对应的Commit来表示当前的Ref对应的版本
-
-
Object : commit / tree / blob 在git里面统称为object,除此之外还有个tag的object
- Blob 存储文件内容信息
- Tree 存储目录树信息
- Commit 存储提交信息,一个commit可以对应唯一的版本代码
- Tag 存储附注标签信息
-
怎么串联三个object的呢
- 通过Commit寻找到Tree信息,每个Commit都会存储对应的Tree ID.
- 通过Tree的存储信息,获取对性的目录树信息
- 从tree中获取blob的ID,通过Blob ID获取对应的文件内容
3.6 分支
查看分支 git branch
这时会出现所有分支
新建分支 git branch + 分支名
切换分支git checkout + 分支名
3.7 修改历史版本
-
commit --amend
通过这个命令可以修改最近的一次的commit信息,但是修改后commit id会变 -
git rebase -i HEAD~3
可以实现对最近三个commit的修改- 合并commit
- 修改具体的commit message
- 删除某个commit
filter --branch
该命令可以指定删除所有提交中的某个文件或者全局修改邮箱地址等操作git fsck --lost-found
查询悬空的commit
3.8 Git GC
-
GC
- 通过
git gc
命令,可以删除一些不需要的 object,以及会对object进行一些打包压缩来减少仓库的体积。
- 通过
-
Reflog
reflog
是用于记录操作日志,防止误操作后数据丢失,通过reflog
来找到丢失的数据,手动将日志设置为过期
-
指定时间
git gc prune=now
指定的是修剪多久之前的对象,默认是两周前
3.9 完整视图
3.10 代码同步 clone & Pull & Push
Git Clone
将代码从远端拉取到本地
Git Fetch
将远端某些分支最新代码拉取到本地,不会执行merge
操作,会修改refs/remote内的分支信息,如果需要和本地代码合并需要手动操作。
-
Git 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
来完成强制推送,一般不推荐主干分支进行该操作,正常都应该解决冲突后再进行推送。
- 如果本地的 commit记录和远端的commit历史不一致,则会产生冲突,比如
-
推送规则限制
- 可以通过设置保护分支,配置一些保护规则,防止误操作,或者一些不合规得操作出现,导致代码丢失
-
四、Git 研发流程
4.1 不同的工作流
类型 | 代表平台 | 特点 | 合入方式 |
---|---|---|---|
集中式工作流 | Gerrit / SVN | 只依托于主干分支(master)进行开发,不存在其他分支 | Fast-forward |
分支管理工作流 | Github / Gitlab | 可以定义不同特性的开发分支,上线分支, 在开发分支完成开发后再通过MR/PR合入主干分支 | 自定义,Fast-Forward or Three- Way Merge都可以 |
4.2 集中式工作流
Gerrit 平台
- Gerrit是由Google开发的一款代码托管平台, 主要的特点就是能够很好的进行代码评审。在aosp (android open source project)中使用的很广,Gerrit 的开发流程就是一种集中式工作流。
- 围绕主分支进行开发
-
工作方式
- 获取远端master 代码
- 直接在master分支完成修改
- 提交前拉取最新的master代码和本地代码进行合并(使用rebase),如果有冲突需要解决冲突
- 提交本地代码到master
-
基本原理
- 依托于Change ID概念,每个提交生成一个单独的代码评审。
- 提交上去的代码不会存储在真正的refs/heads/下的分支中,而是存在一个refs/for/的引用下。
- 通过refs/meta/config下的文件存储代码的配置,包括权限,评审等配置,每个Change都必须要 完成Review后才能合入。
-
优点
- 提供强制的代码评审机制, 保证代码的质量
- 提供更丰富的权限功能,可以针对分支做细粒度的权限管控
- 保证master的历史整洁性
- Aosp 多仓的场景支持更好
-
缺点
- 开发人员较多的情况下,更容易出现冲突
- 对于多分支的支持较差,想要区分多个版本的线上代码时,更容易出现问题
- 一般只有管理员才能创建仓库,比较难以在项目之间形成代码复用,比如类似的fork操作就不支持。
4.3 分支管理工作流
分支管理工作流 | 特点 |
---|---|
Git Flow | 分支类型丰富,规范严格 |
Github Flow | 只有主干分支和开发分支,规则简单 |
Gitlab Flow | 在主干分支和开发分支之上构建环境分支,版本分支,满足不同发布or环境的需要 |
4.3.1 GitFlow
Git Flow时比较早期出现的分支管理策略。
-
包含五种类型的分支
- Master:主干分支
- Develop:开发分支
- Feature:特性分支
- Release:发布分支
- Hotfix:热修复分支
-
优点
- 如果能按照定义的标准严格执行,代码会很清晰,并且很难出现混乱。
-
缺点
- 流程过于复杂,上 线的节奏会比较慢。 由于太复杂,研发容易不按照标准执行,从而导致代码出现混乱。
4.3.2 Github Flow
-
Github的工作流,只有一 个主干分支,基于Pull Request往主干分支中提交代码。
-
选择团队合作的方式:
- owner创建好仓库后,其他用户通过Fork的方式来创建自己的仓库,并在fork的仓库.上进行开发
- owner创建好仓库后,统一给团队内成员分配权限,直接在同一个仓库内进行开发
-
可以通过进行一些保护分支设置,来限制合入的策略,以及限制直接的push操作。
Require a pull request before merging
合并前需要拉取请求,不能直接提交Require conversation resolution before merging
讨论需要解决Require linear history
不要merge节点Include administrators
damin也要遵守
4.3.3 Gitlab Flow
- Gitlab推荐的工作流是在GitFlow和Github Flow . 上做出优化,既保持了单一主分支的简便,又可以适应不同的开发环境。
- 原则: upstream first.上游优先 只有在上游分支采纳的代码才可以进入到下游分支,一般上游分支就是master.
4.4 如何合并代码
-
Three Way Merge
- 三方合并, 会产生一个新的merge节点
git merge "XX" --no-ff
-
Fast Forward Merge
- 不会产生一个merge节点,合并后保持一个线性历史,如果target分支有了更新,则需要通过rebase操作更新 source branch后才可以合入。
git merge "XX" --ff-only
4.5 选择合适的工作流
-
选择原则
- 没有最好的, 只有最合适的
-
针对小型团队合作,推荐使用Github 工作流即可
- 尽量保证少量多次, 最好不要一次性提交 上千行代码
- 提交Pull Request后最少需要保证有CR后再合入
- 主干分支尽量保持整洁,使用fast-forward合入方式,合入前进行rebase
\