前言
在本系列的前两篇博文中,笔者对 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 ( |
Header
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
对本次提交的详细描述。
Footer
- 不兼容变动(需要说明变动信息)
- 关闭issue(需要输入issue信息)
使用 Git 时常会遇到的各种突发状况
git stash
【1】场景重现 one:当正在 feature 分支上开发某个新功能,这时项目中出现一个 bug,需要紧急修复,但是正在开发的内容只是完成一半,还不想提交,这时可以用 git stash 命令将修改的内容保存至堆栈区,然后顺利切换到 hotfix 分支进行 bug 修复,修复完成后,再次切回到 feature 分支,从堆栈中恢复刚刚保存的内容。
【2】场景重现 two:由于疏忽,本应该在 feature 分支开发的内容,却在 develop 上进行了开发,需要重新切回到 feature 分支上进行开发,可以用 git stash 将内容保存至堆栈中,切回到 feature 分支后,再次恢复内容即可。
1 | # 1. 创建 feature 分支 |
git stash 常用命令指南
1 | # 保存,save为可选项,message为本次保存的注释 |
git rebase
不知怎么,git rebase 命令被赋予了一个神奇的污毒声誉,初学者应该远离它,但它实际上可以让开发团队在使用时更加轻松。
Rebase 的黄金法则:git rebase 的黄金法则是永远不要在公共分支上使用它。
【1】场景重现 one:当你在功能分支上开发新 feature 时,然后另一个团队成员在 master 分支提交了新的 commits,这会发生什么?这会导致分叉的历史记录,对于这个问题,使用 Git 作为协作工具的任何人来说都应该很熟悉。现在,假设在 master 分支上的新提交与你正在开发的 feature 相关。需要将新提交合并到你的 feature 分支中,你可以有两个选择:merge 或者 rebase。
Merge 方式:最简单的方式是通过 git merge 命令将 master 分支合并到 feature 分支中
1 | $ git checkout feature |
这会在 feature 分支中创建一个新的 merge commit,它将两个分支的历史联系在一起。使用 merge 是很好的方式,因为它是一种 非破坏性的 操作,现有分支不会以任何方式被更改;另一方面,这也意味着 feature 分支每次需要合并上游更改时,它都将产生一个额外的合并提交。如果master 提交非常活跃,这可能会严重污染你的 feature 分支历史记录。尽管可以使用高级选项 git log 缓解此问题,但它可能使其他开发人员难以理解项目的历史记录。
Rebase 方式:作为 merge 的替代方法,你可以使用以下命令将 master 分支合并到 feature分支上
1 | $ git checkout feature |
这会将整个 feature 分支移动到 master 分支的顶端,从而有效地整合了所有 master 分支上的提交。但是,与 merge 提交方式不同,rebase 通过为原始分支中的每个提交创建全新的 commits 来 重写项目历史记录。
rebase 的主要好处是可以获得更清晰的项目历史。首先,它消除了 git merge 所需的不必要的合并提交;其次,正如你在上图中所看到的,rebase 会产生完美线性的项目历史记录,你可以在 feature 分支上没有任何分叉的情况下一直追寻到项目的初始提交。这样可以通过命令 git log,git bisect 和 gitk 更容易导航查看项目。
【2】场景重现 two:当你在功能分支上开发新 feature 时,多次提交了记录,这时,想要在在合并 feature 分支到 master 之前清理其杂乱的历史记录。
交互式 rebase 使你有机会在将 commits 移动到新分支时更改这些 commits。这比自动 rebase 更强大,因为它提供了对分支提交历史的完全控制。
要使用交互式 rebase,需要使用 git rebase 和 -i 选项:
1 | $ git checkout feature |
这将打开一个文本编辑器,列出即将移动的所有提交:
1 | pick 33d5b7a Message for commit #1 |
此列表准确定义了执行 rebase 后分支的外观。通过更改 pick命令或重新排序条目,你可以使分支的历史记录看起来像你想要的任何内容。例如,如果第二次提交 fix 了第一次提交中的一个小问题,您可以使用以下 fixup 命令将它们浓缩为一个提交:
1 | pick 33d5b7a Message for commit #1 |
保存并关闭文件时,Git将根据您的指示执行 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 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 revert
git revert 撤销一个提交的同时会创建一个新的提交。这是一个安全的方法,因为它不会重写提交历史。
【1】场景重现 one:改完代码匆忙提交,上线发现有问题,怎么办?赶紧回滚。改完代码测试也没有问题,但是上线发现你的修改影响了之前运行正常的代码报错,必须回滚。
1 | # 撤销指定 commit 到当前 HEAD 之间所有的变化 |
git revert 用于反转提交,用一个新提交来撤销某次提交,执行 git revert 命令时要求工作树必须是干净的。git revert 之后你再 git push 既可以把线上的代码更新。git revert 是放弃指定提交的修改,但是会生成一次新的提交,需要填写提交注释,以前的历史记录都在。
强烈推荐
如果你不能很好的应用 Git,那么这里为你提供一个非常棒的 Git 在线练习工具 Learn Git Branching。
参考博文
[1]. Oh Shit, Git!?!