Git 在团队中的最佳实践(三):如何优雅的使用 Git?

前言

在本系列的前两篇博文中,笔者对 Git 以及 Git flow 进行了大致的介绍,相信各位读者已经对 Git 有了大致的了解。但是,在我们的日常工作中使用 Git 时常会遇到的各种突发状况,那么我们应该怎么合理的应对这些状况呢?俗话说,无规矩不成方圆,在团队协作中,如何规范 Git Commit 呢?本文将针对以上问题展开讨论,探讨一下在日常工作中,我们应该如何优雅的使用 Git?

你可能会忽略的 Git 提交规范

无规矩不成方圆,编程也一样。如果在团队协作中,大家都张扬个性,那么代码将会是一团糟,好好的项目就被糟践了。不管是开发还是日后维护,都将是灾难。对于 Git Commit 同样如此,统一 Git Commit 规范可以方便管理团队代码,方便后续进行 code review 以及生成 change log;统一 Git Commit 规范容易理解提交的信息。

分支规范

根据 Git flow 工作流分支模型将我们开发分支规范为五大分支:

  • Master 分支 - 生产分支:最为稳定功能比较完整的随时可发布的代码,即代码开发完成,经过测试,没有明显的 bug,才能合并到 master 中。
  • Develop 分支 - 开发分支:用作平时开发的主分支,并一直存在,永远是功能最新最全的分支,所有的 feature、release 分支都是从 develop 分支上拉的。
  • Feature 分支 - 功能分支:这个分支主要是用来开发新的功能,一旦开发完成,通过测试没问题,我们合并回 develop 分支进入下一个 release。
  • Release 分支 - 发布分支:用于发布准备的专门分支。当开发进行到一定程度,或者说快到了既定的发布日,可以发布时,建立一个 release 分支并指定版本号(可以在 finish 的时候添加)。开发人员可以对 release 分支上的代码进行集中测试和修改 bug。全部完成经过测试没有问题后,将 release 分支上的代码合并到 master 分支和 develop 分支。
  • Hotfix 分支 - 热修复分支:用于修复线上代码的 bug。从 master 分支上拉,完成 hotfix 后,打上 tag 我们合并回 master 和 develop 分支。

标签规范

采用三段式: v版本. 里程碑. 序号,例如 v1.2.1

  • 项目结构发生重大修改,增加第一个数字
  • 发布新的功能,增加第二个数字
  • 修复项目中的 bug,修改第三个数字

Git Commit 信息规范

目前一般采用 Angular 的提交信息规范:信息分为 Header、Body、Footer 三部分

例子:

1
refactor: Restructure SQLRecognizer and UndoExecutor (#1883)

Header 信息分为三部分 type(scope): subject

type(必须),用于说明 Git 提交信息的类别,有以下几个分类

Type 说明
feat 新增功能
fix 修复 bug
docs 修改文档
refactor 重构代码,未新增任何功能或修复任何 bug
build 改变构建流程、新增依赖库
style 仅对样式做出修改(如空格和代码缩进等,不对逻辑进行修改)
perf 改善性能的修改
chore 非 src 或 test 下代码的修改
test 测试用例的修改
ci 自动化流程配置修改
revert 回滚到上一个版本

scope(可选),用于说明 commit 的影响范围,比如数据层、控制层、视图层等等,视项目不同而不同。

subject(必须),commit 的信息主题,尽量言简意赅,说明提交代码的主要变化。

Body

对本次提交的详细描述。

  • 不兼容变动(需要说明变动信息)
  • 关闭issue(需要输入issue信息)

使用 Git 时常会遇到的各种突发状况

git stash

【1】场景重现 one:当正在 feature 分支上开发某个新功能,这时项目中出现一个 bug,需要紧急修复,但是正在开发的内容只是完成一半,还不想提交,这时可以用 git stash 命令将修改的内容保存至堆栈区,然后顺利切换到 hotfix 分支进行 bug 修复,修复完成后,再次切回到 feature 分支,从堆栈中恢复刚刚保存的内容。

【2】场景重现 two:由于疏忽,本应该在 feature 分支开发的内容,却在 develop 上进行了开发,需要重新切回到 feature 分支上进行开发,可以用 git stash 将内容保存至堆栈中,切回到 feature 分支后,再次恢复内容即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 1. 创建 feature 分支
$ git flow feature start some-feature

# 2.在 feature 分支上开发某个新功能
...

# 3. git stash会把所有未提交的修改(包括暂存的和非暂存的)都保存起来,用于后续恢复当前工作目录,当前的工作目录就干净了。
$ git stash save "feat: add user (#1983)"

# 4. 创建 hotfix 分支
$ git flow hotfix start 0.1.1

# 5. 在 hotfix 分支上进行 bug 修复
...

# 6. 完成 bug 修复,提交 hotfix 分支
$ git flow hotfix finish --no-ff 0.1.1

# 7. 切换到 feature 分支
$ git checkout some-feature

# 8. 恢复工作进度到工作区,此命令的 stash@{num} 是可选项,在多个工作进度中可以选择恢复,不带此项则默认恢复最近的一次进度相当于 git stash pop stash@{0}
$ git stash pop stash@{num}

git stash 常用命令指南

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 保存,save为可选项,message为本次保存的注释
$ git stash [save message]

# 所有保存的记录列表
$ git stash list

# 恢复,num是可选项,通过git stash list可查看具体值。只能恢复一次
$ git stash pop stash@{num}

# 恢复,num是可选项,通过git stash list可查看具体值。可回复多次
$ git stash apply stash@{num}

# 删除某个保存,num是可选项,通过git stash list可查看具体值
$ git stash drop stash@{num}

# 查看堆栈中最新保存的 stash 和当前目录的差异,num是可选项,通过git stash list可查看具体值
$ git stash show stash@{num}

# 删除所有保存
$ git stash clear

git rebase

不知怎么,git rebase 命令被赋予了一个神奇的污毒声誉,初学者应该远离它,但它实际上可以让开发团队在使用时更加轻松。

Rebase 的黄金法则:git rebase 的黄金法则是永远不要在公共分支上使用它。

【1】场景重现 one:当你在功能分支上开发新 feature 时,然后另一个团队成员在 master 分支提交了新的 commits,这会发生什么?这会导致分叉的历史记录,对于这个问题,使用 Git 作为协作工具的任何人来说都应该很熟悉。现在,假设在 master 分支上的新提交与你正在开发的 feature 相关。需要将新提交合并到你的 feature 分支中,你可以有两个选择:merge 或者 rebase。

A forked commit history

Merge 方式:最简单的方式是通过 git merge 命令将 master 分支合并到 feature 分支中

1
2
3
4
5
$ git checkout feature
$ git merge master

# 或者将其浓缩为一行命令
$ git merge feature master

这会在 feature 分支中创建一个新的 merge commit,它将两个分支的历史联系在一起。使用 merge 是很好的方式,因为它是一种 非破坏性的 操作,现有分支不会以任何方式被更改;另一方面,这也意味着 feature 分支每次需要合并上游更改时,它都将产生一个额外的合并提交。如果master 提交非常活跃,这可能会严重污染你的 feature 分支历史记录。尽管可以使用高级选项 git log 缓解此问题,但它可能使其他开发人员难以理解项目的历史记录。

Merging master into the feature branch

Rebase 方式:作为 merge 的替代方法,你可以使用以下命令将 master 分支合并到 feature分支上

1
2
3
4
5
$ git checkout feature
$ git rebase master

# 或者将其浓缩为一行命令
$ git rebase master feature

这会将整个 feature 分支移动到 master 分支的顶端,从而有效地整合了所有 master 分支上的提交。但是,与 merge 提交方式不同,rebase 通过为原始分支中的每个提交创建全新的 commits 来 重写项目历史记录。

Rebasing the feature branch onto master

rebase 的主要好处是可以获得更清晰的项目历史。首先,它消除了 git merge 所需的不必要的合并提交;其次,正如你在上图中所看到的,rebase 会产生完美线性的项目历史记录,你可以在 feature 分支上没有任何分叉的情况下一直追寻到项目的初始提交。这样可以通过命令 git log,git bisect 和 gitk 更容易导航查看项目。

【2】场景重现 two:当你在功能分支上开发新 feature 时,多次提交了记录,这时,想要在在合并 feature 分支到 master 之前清理其杂乱的历史记录。

交互式 rebase 使你有机会在将 commits 移动到新分支时更改这些 commits。这比自动 rebase 更强大,因为它提供了对分支提交历史的完全控制。

要使用交互式 rebase,需要使用 git rebase 和 -i 选项:

1
2
$ git checkout feature
$ git rebase -i master

这将打开一个文本编辑器,列出即将移动的所有提交:

1
2
3
pick 33d5b7a Message for commit #1
pick 9480b3d Message for commit #2
pick 5c67e61 Message for commit #3

此列表准确定义了执行 rebase 后分支的外观。通过更改 pick命令或重新排序条目,你可以使分支的历史记录看起来像你想要的任何内容。例如,如果第二次提交 fix 了第一次提交中的一个小问题,您可以使用以下 fixup 命令将它们浓缩为一个提交:

1
2
3
pick 33d5b7a Message for commit #1
fixup 9480b3d Message for commit #2
pick 5c67e61 Message for commit #3

保存并关闭文件时,Git将根据您的指示执行 rebase,从而产生如下所示的项目历史记录:

Squashing a commit with an interactive rebase

消除这种无意义的提交使你的功能历史更容易理解。这是 git merge 根本无法做到的事情。至于 commits 条目前的 pick( 保留该 commit )、fixup( 将该 commit 和前一个 commit 合并,但我不要保留该提交的注释信息 )、squash( 将该 commit 和前一个 commit 合并 ) 等命令,在 git 目录执行 git rebase -i 即可查看到,大家按需重排或合并提交即可,注释说明非常清晰,在此不做过多说明。

git cherry-pick

git cherry-pick 可以理解为” 挑拣” 提交,它会获取某一个分支的单笔提交,并作为一个新的提交引入到你当前分支上。 当我们需要在本地合入其他分支的提交时,如果我们不想对整个分支进行合并,而是只想将某一次提交合入到本地当前分支上,那么就要使用 git cherry-pick 了。

【1】场景重现 one:当正在 feature 分支上开发某个新功能,并且进行了多个提交。这时,你切到另外一个 feature 分支,想把之前 feature 分支上的某个提交复制过来,怎么办?这时候,神奇的 git cherry-pick 就闪亮的登场了。

1
$ git cherry-pick c2 c4

git cherry-pick 流程图

git reset

git reset 通过把分支记录回退几个提交记录来实现撤销改动。你可以将这想象成“改写历史”。git reset 向上移动分支,原来指向的提交记录就跟从来没有提交过一样。git reset是指将 HEAD 指针指到指定提交,历史记录中不会出现放弃的提交记录。

【1】场景重现 one:有时候,我们用 Git 的时候有可能 commit 提交代码后,发现这一次 commit 的内容是有错误的,那么有两种处理方法:1、修改错误内容,再次 commit 一次;2、使用 git reset 命令撤销这一次错误的 commit。第一种方法比较直接,但会多次一次 commit 记录。而我个人更倾向第二种方法,错误的 commit 没必要保留下来。那么今天来说一下 git reset。

Git reset 命令有三个主要选项:git reset –soft; git reset –mixed; git reset –hard;

  • git reset –soft:软合并 - 保留工作目录,并把重置 HEAD 所带来的新的差异放进暂存区。重置位置的同时,保留 working Tree 工作目录和 index 暂存区的内容,只让 repository 中的内容和 reset 目标节点保持一致,因此原节点和 reset 节点之间的【差异变更集】会放入 index 暂存区中 (Staged files)。所以效果看起来就是工作目录的内容不变,暂存区原有的内容也不变,只是原节点和 Reset 节点之间的所有差异都会放到暂存区中。
  • git reset –mixed: 混合合并(默认) - 保留工作目录, 并清空暂存区。重置位置的同时,只保留 Working Tree 工作目录的內容,但会将 Index 暂存区 和 Repository 中的內容更改和 reset 目标节点一致,因此原节点和 Reset 节点之间的【差异变更集】会放入 Working Tree 工作目录中。所以效果看起来就是原节点和 Reset 节点之间的所有差异都会放到工作目录中。
  • git reset –hard:强行合并 - 重置 stage 区和工作目录。重置位置的同时,直接将 working Tree 工作目录、 index 暂存区及 repository 都重置成目标 Reset 节点的內容, 所以效果看起来等同于清空暂存区和工作区。

git reset 流程图

git revert

git revert 撤销一个提交的同时会创建一个新的提交。这是一个安全的方法,因为它不会重写提交历史。

【1】场景重现 one:改完代码匆忙提交,上线发现有问题,怎么办?赶紧回滚。改完代码测试也没有问题,但是上线发现你的修改影响了之前运行正常的代码报错,必须回滚。

1
2
3
4
5
# 撤销指定 commit 到当前 HEAD 之间所有的变化
$ git revert [commit]..HEAD

# 撤销指定 commit 到当前 HEAD 之间所有的变化 [不自动生成多个新的 commit,而是用一个 commit 完成]
$ git revert -n [commit]..HEAD

git revert 用于反转提交,用一个新提交来撤销某次提交,执行 git revert 命令时要求工作树必须是干净的。git revert 之后你再 git push 既可以把线上的代码更新。git revert 是放弃指定提交的修改,但是会生成一次新的提交,需要填写提交注释,以前的历史记录都在。

git revert 流程图

强烈推荐

如果你不能很好的应用 Git,那么这里为你提供一个非常棒的 Git 在线练习工具 Learn Git Branching

参考博文

[1]. Oh Shit, Git!?!


Git 在团队中的最佳实践系列


谢谢你长得那么好看,还打赏我!😘
0%