1、Git开发模式
在使用 Git 开发时,有 4 种常用的工作流,也叫开发模式,按演进顺序分为集中式工作流、功能分支工作流、Git Flow 工作流和 Forking 工作流。接下来,我会按演进顺序分别介绍这 4 种工作流
1.1、集中式工作流
我们先来看看集中式工作流,它是最简单的一种开发方式。集中式工作流的工作模式如下图所示 :
A、B、C 为 3 位开发者,每位开发者都在本地有一份远程仓库的拷贝:本地仓库。A、B、C 在本地的 master 分支开发完代码之后,将修改后的代码 commit 到远程仓库,如果有冲突就先解决本地的冲突再提交。在进行了一段时间的开发之后,远程仓库 master 分支的日志可能如下图所示:
和其他工作流相比,集中式工作流程的代码管理较混乱,容易出问题,因此适合用在团队人数少、开发不频繁、不需要同时维护多个版本的小项目中。当我们想要并行开发多个功能时,这种工作流就不适用了,这时候怎么办呢?我们接下来看功能分支工作流
1.2、功能分支工作流
功能分支工作流基于集中式工作流演进而来。在开发新功能时,基于 master 分支新建一个功能分支,在功能分支上进行开发,而不是直接在本地的 master 分支开发,开发完成之后合并到 master 分支,如下图所示:
相较于集中式工作流,这种工作流让不同功能在不同的分支进行开发,只在最后一步合并到 master 分支,不仅可以避免不同功能之间的相互影响,还可以使提交历史看起来更加简洁
还有,在合并到 master 分支时,需要提交 PR(pull request),而不是直接将代码 merge 到 master 分支。PR 流程不仅可以把分支代码提供给团队其他开发人员进行 CR(Code Review),还可以在 PR 页面讨论代码。通过 CR ,我们可以确保合并到 master 的代码是健壮的;通过 PR 页面的讨论,可以使开发者充分参与到代码的讨论中,有助于提高代码的质量,并且提供了一个代码变更的历史回顾途径
那么,功能分支工作流具体的开发流程是什么呢?我们一起来看下
1.基于 master 分支新建一个功能分支,功能分支可以取一些有意义的名字,便于理解,例如 feature/rate-limiting
git checkout -b feature/rate-limiting
2.在功能分支上进行代码开发,开发完成后 commit 到功能分支
git add limit.go
git commit -m "add rate limiting"
3.将本地功能分支代码 push 到远程仓库
git push origin feature/rate-limiting
4.在远程仓库上创建 PR(例如:GitHub)
进入 GitHub 平台上的项目主页,点击 Compare & pull request 提交 PR,如下图所示 :
点击 Compare & pull request 后会进入 PR 页面,在该页面中可以根据需要填写评论,最后点击 Create pull request 提交 PR
5.代码管理员收到 PR 后,可以 CR 代码,CR 通过后,再点击 Merge pull request 将 PR 合并到 master,如下图所示 :
图中的“Merge pull request” 提供了 3 种 merge 方法:
- Create a merge commit:GitHub 的底层操作是 git merge --no-ff。feature 分支上所有的 commit 都会加到 master 分支上,并且会生成一个 merge commit。这种方式可以让我们清晰地知道是谁做了提交,做了哪些提交,回溯历史的时候也会更加方便
- Squash and merge:GitHub 的底层操作是 git merge --squash。Squash and merge 会使该 pull request 上的所有 commit 都合并成一个 commit ,然后加到 master 分支上,但原来的 commit 历史会丢失。如果开发人员在 feature 分支上提交的 commit 非常随意,没有规范,那么我们可以选择这种方法来丢弃无意义的 commit。但是在大型项目中,每个开发人员都应该是遵循 commit 规范的,因此我不建议你在团队开发中使用 Squash and merge
- Rebase and merge:GitHub 的底层操作是 git rebase。这种方式会将 pull request 上的所有提交历史按照原有顺序依次添加到 master 分支的头部(HEAD)。因为 git rebase 有风险,在你不完全熟悉 Git 工作流时,我不建议 merge 时选择这个
通过分析每个方法的优缺点,在实际的项目开发中,我比较推荐你使用 Create a merge commit 方式
从刚才讲完的具体开发流程中,我们可以感受到,功能分支工作流上手比较简单,不仅能使你并行开发多个功能,还可以添加 code review,从而保障代码质量。当然它也有缺点,就是无法给分支分配明确的目的,不利于团队配合。它适合用在开发团队相对固定、规模较小的项目中。接下来我们要讲的 Git Flow 工作流以功能分支工作流为基础,较好地解决了上述问题
1.3、Git Flow 工作流
Git Flow 工作流是一个非常成熟的方案,也是非开源项目中最常用到的工作流。它定义了一个围绕项目发布的严格分支模型,通过为代码开发、发布和维护分配独立的分支来让项目的迭代流程更加顺畅,比较适合大型的项目或者迭代速度快的项目。接下来,我会通过介绍 Git Flow 的 5 种分支和工作流程,来给你讲解 GIt Flow 是如何工作的
Git Flow 的 5 种分支
Git Flow 中定义了 5 种分支,分别是 master、develop、feature、release 和 hotfix。其中,master 和 develop 为常驻分支,其他为非常驻分支,不同的研发阶段会用到不同的分支。这 5 种分支的详细介绍见下表
分支名 | 描述 |
---|---|
master | 该分支上的最新代码永远是发布状态,不能直接在该分支上开发。master分支每合并一个hotfix/release分支,都会打一个版本标签 |
develop | 该分支上的代码是开发中的最新代码,该分支只做合并操作,不做直接在该分支上开发 |
feature | 在研发阶段用来做功能开发。一个新功能会基于 develop 分支新建一个 feature 分支,分支命名建议命名为 : feature/xxx-xxx,功能开发完成之后,会合并到 develop 分支并删除,还有一个点需要你注意,feature分支在申请合并之前,最好是先pull一下develop分支,看一下有没有冲突,如果有就先解决冲突再申请合并 |
release | 在发布节点用作版本发布的预发布分支,基于 develop 分支创建,分支名建议命名为 : release/xxx-xxx。例如 : v1.0.0 版本的功能全部开发测试完成后,提交到develop 分支,然后基于develop分支创建 release/1.0.0 分支,并提交测试,测试中遇到的问题在release分支修改,最终通过测试后,将release分支合并到master和develop,并在master分支打上v1.0.0的版本标签,最后删除release/1.0.0分支 |
hotfix | 在维护阶段用作紧急bug修复分支,在master分支上创建,修复完成后合并到master。分支名建议命名为hotfix/xxx-xxx。例如 : 当线上某个版本出现Bug后,从master检出对应版本的代码,创建hotfix分支,并在hotfix分支修复问题。问题修复后,将hotfix分支合并到master和develop分支,并在master分支上修复后的版本标签,最后删除hotfix分支 |
Git Flow 开发流程
这里我们用一个实际的例子来演示下 Git Flow 的开发流程。场景如下:
a. 当前版本为:0.9.0
b. 需要新开发一个功能,使程序执行时向标准输出输出“hello world”字符串
c. 在开发阶段,线上代码有 Bug 需要紧急修复
假设我们的 Git 项目名为 gitflow-demo,项目目录下有 2 个文件,分别是 README.md 和 main.go,内容如下 :
package main
import "fmt"
func main() {
fmt.Println("callmainfunction")
}
具体的开发流程有 12 步,你可以跟着以下步骤操作练习
1.创建一个常驻的分支:develop
git checkout -b develop master
2.基于 develop 分支,新建一个功能分支:feature/print-hello-world
git checkout -b feature/print-hello-world develop
3.feature/print-hello-world 分支中,在 main.go 文件中添加一行代码
fmt.Println("Hello"),添加后的代码如下
package main
import "fmt"
func main() {
fmt.Println("callmainfunction")
fmt.Println("Hello")
}
4.紧急修复 Bug
我们正处在新功能的开发中(只完成了 fmt.Println("Hello")而非 fmt.Println("Hello World"))突然线上代码发现了一个 Bug,我们要立即停止手上的工作,修复线上的 Bug,步骤如下 :
git stash # 1. 开发工作只完成了一半,还不想提交,可以临时保存修改至堆栈区
git checkout -b hotfix/print-error master # 2. 从 master 建立 hotfix 分支
vi main.go # 3. 修复 bug,callmainfunction -> call main function
git commit -a -m 'fix print message error bug' # 4. 提交修复
git checkout develop # 5. 切换到 develop 分支
git merge --no-ff hotfix/print-error # 6. 把 hotfix 分支合并到 develop 分支
git checkout master # 7. 切换到 master 分支
git merge --no-ff hotfix/print-error # 8. 把 hotfix 分支合并到 master
git tag -a v0.9.1 -m "fix log bug" # 9. master 分支打 tag
go build -v . # 10. 编译代码,并将编译好的二进制更新到生产环境
git branch -d hotfix/print-error # 11. 修复好后,删除 hotfix/xxx 分支
git checkout feature/print-hello-world # 12. 切换到开发分支下
git merge --no-ff develop # 13. 因为 develop 有更新,这里最好同步更新下
git stash pop # 14. 恢复到修复前的工作状态
5.继续开发
在 main.go 中加入 fmt.Println("Hello World")
6.提交代码到 feature/print-hello-world 分支。
git commit -a -m "print 'hello world'"
7.在 feature/print-hello-world 分支上做 code review
首先,我们需要将 feature/print-hello-world push 到代码托管平台,例如 GitHub 上
git push origin feature/print-hello-world
然后,我们在 GitHub 上,基于 feature/print-hello-world 创建 pull request,如下图所示 :
创建完 pull request 之后,我们就可以指定 Reviewers 进行 code review,如下图所示 :
8.code review 通过后,由代码仓库 matainer 将功能分支合并到 develop 分支
git checkout develop
git merge --no-ff feature/print-hello-world
9.基于 develop 分支,创建 release 分支,测试代码
git checkout -b release/1.0.0 develop
go build -v . # 构建后,部署二进制文件,并测试
10.测试失败,因为我们要求打印“hello world”,但打印的是“Hello World”,修复的时候
我们直接在 release/1.0.0 分支修改代码,修改完成后,提交并编译部署
git commit -a -m "fix bug"
go build -v .
11.测试通过后,将功能分支合并到 master 分支和 develop 分支
git checkout develop
git merge --no-ff release/1.0.0
git checkout master
git merge --no-ff release/1.0.0
git tag -a v1.0.0 -m "add print hello world" # master 分支打 tag
12.删除 feature/print-hello-world 分支,也可以选择性删除 release/1.0.0 分支
git branch -d feature/print-hello-world
亲自操作一遍之后,你应该会更了解这种模式的优缺点。它的缺点,就是你刚才已经体会到的,它有一定的上手难度。不过 Git Flow 工作流还是有很多优点的:Git Flow 工作流的每个分支分工明确,这可以最大程度减少它们之间的相互影响。因为可以创建多个分支,所以也可以并行开发多个功能。另外,和功能分支工作流一样,它也可以添加 code review,保障代码质量
因此,Git Flow 工作流比较适合开发团队相对固定,规模较大的项目
1.4、Forking 工作流
上面讲的 Git Flow 是非开源项目中最常用的,而在开源项目中,最常用到的是 Forking 工作流,例如 Kubernetes、Docker 等项目用的就是这种工作流。这里,我们先来了解下 fork 操作
fork 操作是在个人远程仓库新建一份目标远程仓库的副本,比如在 GitHub 上操作时,在项目的主页点击 fork 按钮(页面右上角),即可拷贝该目标远程仓库。Forking 工作流的流程如下图所示
假设开发者 A 拥有一个远程仓库,如果开发者 B 也想参与 A 项目的开发,B 可以 fork 一份 A 的远程仓库到自己的 GitHub 账号下。后续 B 可以在自己的项目进行开发,开发完成后,B 可以给 A 提交一个 PR。这时候 A 会收到通知,得知有新的 PR 被提交,A 会去查看 PR 并 code review。如果有问题,A 会直接在 PR 页面提交评论,B 看到评论后会做进一步的修改。最后 A 通过 B 的 PR 请求,将代码合并进了 A 的仓库。这样就完成了 A 代码仓库新特性的开发。如果有其他开发者想给 A 贡献代码,也会执行相同的操作
GitHub 中的 Forking 工作流详细步骤共有 6 步(假设目标仓库为 gitflow-demo),你可以跟着以下步骤操作练习
1.Fork 远程仓库到自己的账号下
访问 https://github.com/dataease/dataease,点击 fork 按钮。fork 后的仓库地址为 https://github.com/qiaozhanwei/dataease
2.克隆 fork 的仓库到本地
git clone https://github.com/qiaozhanwei/dataease
cd dataease
git remote add upstream https://github.com/dataease/dataease
git remote set-url --push upstream no_push # Never push to upstream master
git remote -v # Confirm that your remotes make sense
origin https://github.com/qiaozhanwei/dataease (fetch)
origin https://github.com/qiaozhanwei/dataease (push)
upstream https://github.com/qiaozhanwei/dataease (fetch)
upstream https://github.com/qiaozhanwei/dataease (push)
3.创建功能分支
首先,要同步本地仓库的 master 分支为最新的状态(跟 upstream master 分支一致)
git fetch upstream
git checkout master
git rebase upstream/master
然后,创建功能分支
git checkout -b feature/add-function
4.提交 commit
在 feature/add-function 分支上开发代码,开发完代码后,提交 commit
git fetch upstream # commit 前需要再次同步 feature 跟 upstream/master
git rebase upstream/master
git add <file>
git status
git commit
分支开发完成后,可能会有一堆 commit,但是合并到主干时,我们往往希望只有一个(或最多两三个)commit,这可以使功能修改都放在一个或几个 commit 中,便于后面的阅读和维护。这个时候,我们可以用 git rebase 来合并和修改我们的 commit,操作如下 :
git reset HEAD~5
git add .
git commit -am "Here's the bug fix that closes #28"
git push --force
squash 和 fixup 命令,还可以当作命令行参数使用,自动合并 commit
git commit --fixup
git rebase -i --autosquash
5.push 功能分支到个人远程仓库
在完成了开发,并 commit 后,需要将功能分支 push 到个人远程代码仓库,代码如下:
git push -f origin feature/add-function
6.在个人远程仓库页面创建 pull request
提交到远程仓库以后,我们就可以创建 pull request,然后请求 reviewers 进行代码 review,确认后合并到 master。
这里要注意,创建 pull request 时,base 通常选择目标远程仓库的 master 分支。我们已经讲完了 Forking 工作流的具体步骤,你觉得它有什么优缺点呢?
结合操作特点,我们来看看它的优点:Forking 工作流中,项目远程仓库和开发者远程仓库完全独立,开发者通过提交 Pull Request 的方式给远程仓库贡献代码,项目维护者选择性地接受任何开发者的提交,通过这种方式,可以避免授予开发者项目远程仓库的权限,从而提高项目远程仓库的安全性,这也使得任意开发者都可以参与项目的开发
但 Forking 工作流也有局限性,就是对于职能分工明确且不对外开源的项目优势不大。Forking 工作流比较适用于以下三种场景:(1)开源项目中;(2)开发者有衍生出自己的衍生版的需求;(3)开发者不固定,可能是任意一个能访问到项目的开发者
1.5、总结
基于 Git 向你介绍了 4 种开发模式,现在跟我回顾一下吧
- 集中式工作流:开发者直接在本地 master 分支开发代码,开发完成后 push 到远端仓库 master 分支
- 功能分支工作流:开发者基于 master 分支创建一个新分支,在新分支进行开发,开发完成后合并到远端仓库 master 分支
- Git Flow 工作流:Git Flow 工作流为不同的分支分配一个明确的角色,并定义分支之间什么时候、如何进行交互,比较适合大型项目的开发
- Forking 工作流:开发者先 fork 项目到个人仓库,在个人仓库完成开发后,提交 pull request 到目标远程仓库,远程仓库 review 后,合并 pull request 到 master 分支
集中式工作流是最早的 Git 工作流,功能分支工作流以集中式工作流为基础,Git Flow 工作流又是以功能分支工作流为基础,Forking 工作流在 Git Flow 工作流基础上,解耦了个人远端仓库和项目远端仓库
每种开发模式各有优缺点,适用于不同的场景,我总结在下表中:
工作流 | 优点 | 缺点 | 使用场景 |
---|---|---|---|
集中工作流 | 上手最简单 | 代码管理较混乱,容易出问题 | 团队人数少,开发不频繁,不需要同时维护多个版本的小项目 |
功能分支工作流 | 上手比较简单,支持并行开发,支持Code Review | 无法给分支分配明确的目的,不利于团队配合 | 开发团队相对文婷、规模较小的项目 |
GitFlow工作流 | 每个分支分工明确,这可以最大程度减少它们之间的相互影响,可以并行开发,支持Code Review | 一定的上手难度 | 比较适合开发团队相对固定,规模较大的项目 |
Forking工作流 | 完全解耦个人源端仓库和项目远端仓库,最大程度上保证远端仓库的安全 | 对阵只能分工明确且不对外开源项目优势不大 | 比较适合于开源项目中,或者开发者有衍生出自己的衍生版本的需求,或者开发者不固定,可能是任意一个能访问到项目的开发者 |
总的来说,在选择工作流时,我的推荐如下:
- 非开源项目采用 Git Flow 工作流
- 开源项目采用 Forking 工作流
2、commit message规范
注意 : 分支和提交message建议使用英文
分支命名 | 类型 | 提交message |
---|---|---|
feature/xxx | 新增功能 | feat:xxx |
bugfix/xxx(bugId) | Bug 修复 | fix:xxx |
refactor/xxx | 其他代码类的变更,这些变更不属于feat、fix、perf和style,例如简化代码、重命名变量、删除冗余代码等 | refactor:xxx |
performance/xxx | 提供代码性能的变更 | perf:xxx |
style/xxx | 代码格式类的变更,比如用gofmt格式化代码、删除空行等 | style:xxx |
test/xxx | 新增测试用例或更新现有测试用例 | test:xxx |
docs/xxx | 文档类的更新,包括修改用户文档或者开发文档等 | docs:xxx |
ci/xxx | 持续集成和部署相关的改动,比如修改Jenkins、GitLab CI等CI配置文件或者更新systemmd unit文件 | ci:xxx |
chore/xxx | 其他类型,比如构建流程、依赖管理或者辅助工具的变更等 | chore:xxx |
3、多个commit合并到一个commit
qiaozhanwei@qiaozhanweideMacBook-Pro git-test % git log --oneline
8760df9 (HEAD -> master, origin/master, origin/HEAD) feat : README.md update
47601ec feat : README.md update
f4ffa32 Initial commit
比如说要把8760df9、47601ec两个commit合并到一个commit,此时需要找到f4ffa32
git rebase -i f4ffa32
会出现如下的交互页面 :
然后上最上面的commit不动,下面的前缀修改为s即可,如下图 :
wq退出后会进入下一个交互编辑框,如下图 :
此时修改你的commit内容,wq退出即可,比如修改如下 :
最后提交 :
git push --force
查看结果 :
4、修改Commit Message
4.1、修改最近一次提交的 Commit Message
直接使用即可,比较简单,不做示例
git commit --amend
4.2、修改某一次提交的 Commit Message
比如 :
qiaozhanwei@qiaozhanweideMacBook-Pro git-test % git log --oneline
3a8bcc0 (HEAD -> master, origin/master, origin/HEAD) feat : README.md update2
b463731 feat : README.md update-1
f4ffa32 Initial commit
需求,是想让 b463731 这个 commit 修改为 feat : README.md update-1
使用命令 git rebase -i <父 commit ID> :
qiaozhanwei@qiaozhanweideMacBook-Pro git-test % git rebase -i f4ffa32
将要修改的commit id的前缀修改为 r,如下图 :
wq保存退出,修改内容如下 :
wq保存退出,提交
qiaozhanwei@qiaozhanweideMacBook-Pro git-test % git push --force
如下所示 :
qiaozhanwei@qiaozhanweideMacBook-Pro git-test % git log --oneline
212afef (HEAD -> master, origin/master, origin/HEAD) feat : README.md update2
165ce07 feat : README.md update1
f4ffa32 Initial commit
如感兴趣,点赞加关注,谢谢!!!
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。