Gitis a free and open source distributed version control system designed to handle everything from small to very large projects with speed and efficiency.
Git的正确使用姿势与最佳实践
01 Git的前世今生
Git是什么:Gitis 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,通过这些补丁,我们可以计算出每个版本的实际的文件内容。 - 缺点:
RCS这种本地版本控制存在最致命的缺陷就是只能在本地使用,无法进行团队协作,因此使用的场景非常有限,因此衍生出了集中式版本控制。
1.1.2 集中版本控制
- 基本原理:
- 提供一个远端服务来保存文件,所有用户的提交都提交到该服务器中。
- 增量保存每次提交的
Diff,如果提交的增量中和远端现存的文件存在冲突,则需要本地提前解决冲突。
- 优点:
- 学习简单,更容易操作。
- 支持二进制文件,对大文件支持更友好。
- 缺点:
- 本地不存储版本管理的概念,所有提交都只能联上服务器后才可以提交。
- 分支上的支持不够好,对于大型项目团队合作比较困难。
- 用户本地不保存所有版本的代码,如果服务端出现故障,容易导致历史版本的丢失。
1.1.3 分布式版本控制
代表性工具:
Git基本原理:
- 每个库都存有完整的提交历史,可以直接在本地进行代码提交。
- 每次提交记录的都是完整的文件快照,而不是记录增量。
- 通过
Push等操作来完成和远端代码的同步。
优点:
- 分布式开发,每个库都是完整的提交历史,支持本地提交,强调个体。
- 分支管理功能强大,方便团队合作,多人协同开发。
- 校验和机制保证完整性,一般只添加数据,很少执行删除操作,不容易导致代码丢失。
缺点:
- 相对
SVN更复杂,学习成本更高。 - 对于大文件的支持不是特别好(
git-lfs工具可以弥补这个功能)。
- 相对
1.2 Git平台
Github:全球最大的代码托管平台,大部分的开源项目都放在这个平台上。Gitlab:全球最大的开源代码托管平台,项目的所有代码都是开源的,便于在自己的服务器上完成Gitlab的搭建。Gerrit:由 Google 开发的一个代码托管平台,Android这个开源项目就托管在Gerrit之上。
02 Git基本使用方式
2.1 Git目录介绍
2.1.1 Git Config
Git有不同级别的配置:- **全局配置
global**:~/.gitconfig - **系统配置
system**:$(prefix)/etc/gitconfig - **本地配置
local**:.git/config
每个级别的配置可能重复,但是低级别的配置会覆盖高级别的配置。
- **全局配置
2.1.2 常见Git配置
用户名配置:
1
2git config --global user.name "username'
git config --global user.email youremailInstead of配置:1
git config --global url.git@github.com:.insteadOf https://github.com/
Git命令别名配置:1
git config --global alias.cin "commit --amend --no-edit'"
2.2 Git Remote
查看
remote仓库:git remote -v添加
remote仓库:1
2git remote add origin_ssh git@github.com:git/git.git
git remote add origin_http https://github.com/git/git.git同一个
Origin设置不同的Push和FetchURL:1
git remote set-url --add --push origin git@github.com:MY_REPOSITY/git
2.2.1 HTTP Remote
- URL:
https://github.com/git/git.git
2.2.2 SSH Remote
URL:
git@github.com:git/git.git免密配置:
SSH可以通过公私钥的机制,将生成公钥存放在服务端,从而实现免密访问。目前的
Key的类型四种,分别是dsa、rsa、ecdsa、ed25519。默认使用的是rsa,由于一些安全问题,现在已经不推荐使用dsa和rsa了,**优先推荐使用ed25519**。
2.3 Git Add
git add <filename>: 添加文件到暂存区git add命令使用文件或目录的路径作为参数;如果参数是目录的路径,该命令将递归地跟踪该目录下的所有文件。git add是个多功能命令:可以用它开始跟踪新文件,或者把已跟踪的文件放到暂存区,还能用于合并时把有冲突的文件标记为已解决状态等。 将这个命令理解为 “精确地将内容添加到下一次提交中” 而不是 “将一个文件添加到项目中” 要更加合适。
2.4 Git Commit
git commit:创建一个新的提交,并启动你选择的文本编辑器来输入提交说明。git commit -m "message":将提交信息与命令放在同一行,而不必打开文本编辑器来输入说明提交git commit -a:Git会自动把所有已经跟踪过的文件暂存起来一并提交,从而跳过git add步骤
2.5 Objects
Git中的对象可以是 blob、树或提交:- 通过
commit寻找到tree信息,每个commit都会存储对应的tree ID; - 通过
tree存储的信息,获取到对应的目录树信息; - 从
tree中获得blob的ID,通过blob ID获取对应的文件内容。
- 通过
2.6 Refs
Refs文件存储的内容就是对应的Commit ID,因此把Ref当做指针,指向对应的Commit来表示当前Ref对应的版本。refs/heads前缀表示的是分支,除此之外还有其他种类的ref,比如refs/tags前缀表示的是标签。- 分支一般用于开发阶段,是可以不断添加
commit进行迭代的; - 标签一般表示的是一个稳定版本,指向的
commit一般不会变更。
- 分支一般用于开发阶段,是可以不断添加
2.7 Annotation Tag
- 附注标签:一种特殊的
tag,可以给tag提供一些额外的信息。- 通过
git tag -a命令来完成附注标签的创建。
- 通过
2.8 追溯历史版本
- 获取当前版本代码:通过
Ref指向的Commit可以获取唯一的代码版本。 - 获取历史版本代码:
Commit里面会存有parent commit字段,通过commit的串联获取历史版本代码。
2.9 修改历史版本
git commit --amend:通过这个命令可以修改最近的一次commit信息,修改之后commit id会变。rebase:通过git rebase-i HEAD~3可以实现对最近三个commit的修改:- 合并
commit; - 修改具体的
commit message; - 删除某个
commit。
- 合并
filter --branch:该命令可以指定删除所有提交中的某个文件或者全局修改邮箱地址等操作。
2.10 Git GC
通过
git gc命令,可以删除一些不需要的object,以及会对object进行一些打包压缩来减少仓库的体积。Reflog:reflog是用于记录操作日志,防止误操作后数据丢失,通过reflog来找到丢失的数据,手动将日志设置为过期。1
git reflog expire --expire=now --all
指定时间:
git gc --prune=now指定的是修剪多久之前的对象,默认是两周前。
2.11 Git clone & pull & fetch
git clone:拉取完整的仓库到本地目录,可以指定分支,深度。git fetch:将远端某些分支最新代码拉取到本地,不会执行merge操作,会修改refs/remote内的分支信息,如果需要和本地代码合并需要手动操作。git pull:拉取远端某分支,并和本地代码进行合并,操作等同于git fetch+git merge,也可以通过git pull --rebase完成git fetch+git rebase操作。可能存在冲突,需要解决冲突。
2.12 Git push
Push是将本地代码同步至远端的方式。常用命令:git push origin master- 冲突问题:
- 如果本地的
commit记录和远端的commit历史不一致,则会产生冲突,比如git commit-一amendorgit rebase都有可能导致这个问题。 - 如果该分支就自己一个人使用,或者团队内确认过可以修改历史则可以通过
git push origin maste -f来完成强制推送,一般不推荐主干分支进行该操作,正常都应该解决冲突后再进行推送。
- 如果本地的
- 推送规则限制:可以通过保护分支,来配置一些保护规则,防止误操作,或者一些不合规的操作出现,导致代码丢失。
03 Git的研发流程
3.1 不同的工作流
| 类型 | 代表平台 | 特点 | 合入方式 |
|---|---|---|---|
| 集中式工作流 | Gerrit / SVN | 只依托于主干分支进行开发,不存在其他分支 | Fast-Forward |
| 分支管理工作流 | Github / Gitlab | 可以定义不同特性的开发分支,上线分支,在开发分支完成开发后再通过 MR/PR 合入主干分支 | 自定义,Fast-Forward 或者 Three-Way Merge 都可以 |
3.2 集中式工作流
- 只依托于
master分支进行研发活动。 - 工作方式:
- 获取远端
master代码; - 直接在
master分支完成修改; - 提交前拉取最新的
master代码和本地代码进行合并(使用rebase),如果有冲突需要解决冲突; - 提交本地代码到
master。
- 获取远端
3.3 分支管理工作流
| 分支管理工作流 | 特点 |
|---|---|
| Git Flow | 分支类型丰富,规范严格 |
| Github Flow | 只有主干分支和开发分支,规则简单 |
| Gitlab Flow | 在主干分支和开发分支之上构建环境分支,版本分支,满足不同发布 or 环境的需要 |
3.3.1 分支管理工作流-Git Flow
- Git Flow 是比较早期出现的分支管理策略。包含五种类型的分支:
Master:主干分支Develop:开发分支Feature:特性分支Release:发布分支Hotfix:热修复分支
- 优点:如果能按照定义的标准严格执行,代码就会很清晰,并且很难出现混乱。
- 缺点:流程过于复杂,上线的节奏会比较慢。由于太复杂,研发容易不按照标准执行,从而导致代码出现混乱。
3.3.2 分支管理工作流-Github Flow
- Github 的工作流,只有一个主干分支,基于
Pull Request往主干分支中提交代码。 - 选择团队合作的方式:
- owner 创建好仓库后,其他用户通过
fork的方式来创建自己的仓库,并在fork的仓库上进行开发; - owner 创建好仓库后,统一给团队内成员分配权限,直接在同一个仓库内进行开发。
- owner 创建好仓库后,其他用户通过
3.3.3 分支管理工作流-Gitlab Flow
Gitlab推荐的工作流是在GitFlow和Github Flow上做出优化,既保持了单一主分支的简便,又可以适应不同的开发环境。原则:
upstream first(上游优先),只有在上游分支采纳的代码才可以进入到下游分支,一般上游分支就是master。
3.4 代码合并
Fast-Forward:不会产生一个merge节点,合并后保持一个线性历史,如果target分支有了更新,则需要通过rebase操作更新source branch后才可以合入。Three-Way Merge:三方合并,会产生一个新的merge节点。
3.5 如何选择合适的工作流
没有最好的,只有最合适的。
针对小型团队合作,推荐使用
Github Flow即可。- 尽量保证少量多次,最好不要一次性提交上千行代码;
- 提交
Pull Request后最少需要保证有CR后再合入; - 主干分支尽量保持整洁,使用
fast-forward合入方式,合入前进行rebase。
大型团队合作,根据自己的需要指定不同的工作流,不需要局限在某种流程中。