Git 提交规范 (附带git rebase 用法详解)

662 阅读11分钟

一、为什么需要规范?

无规矩不成方圆,编程也一样。

如果你有一个项目,从始至终都是自己写,那么你想怎么写都可以,没有人可以干预你。可是如果在团队协作中,大家都张扬个性,那么代码将会是一团糟,好好的项目就被糟践了。不管是开发还是日后维护,都将是灾难。

这时候,有人提出了何不统一标准,大家都按照这个标准来。于是 ESLintJSHint 等代码工具如雨后春笋般涌现,成为了项目构建的必备良品。

Git Commit 规范可能并没有那么夸张,但如果你在版本回退的时候看到一大段糟心的 Commit,恐怕会懊恼不已吧。所以,严格遵守规范,利人利己。

二、具体规则

先来看看公式:

<type>(<scope>): <subject>复制代码
  • type

    • 用于说明 commit 的类别,只允许使用下面7个标识。

      feat:新功能(feature)
      fix:修补bug
      docs:文档(documentation)
      style: 格式(不影响代码运行的变动)
      refactor:重构(即不是新增功能,也不是修改bug的代码变动)
      test:增加测试
      chore:构建过程或辅助工具的变动复制代码
      
  • scope

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

    • commit 目的的简短描述,不超过50个字符。

      1.以动词开头,使用第一人称现在时,比如change,而不是changed或changes
      2.第一个字母小写
      3.结尾不加句号(.)复制代码
      

规范参考自阮一峰老师的文章:Commit message 和 Change log 编写指南

三、异常处理

我们先来看看这个异常提醒:

INVALID COMMIT MSG: does not match "<type>(<scope>): <subject>" !
jartto:fix bug复制代码

这里之所以报出这个警告,是因为我的提交出现了两个问题:
其一,使用了规范外的关键字;
其二,很细节的问题,jartto:后少了空格;

这时候我才回忆起来,当时提交一直失败,情急之下直接强制提交,所以以后的提交都会抱出这个异常。大致意思就是:

你的之前的 Commit 不合格~你的之前的 Commit 不合格~你的之前的 Commit 不合格

这时候就很烦了,我们只能去将之前的错误修正,那么如何操作呢?

四、如何修改之前的 commit 信息?

其实并不复杂,我们只需要这样做:
1、将当前分支无关的工作状态进行暂存

git stash复制代码

2、将 HEAD 移动到需要修改的 commit

git rebase 9633cf0919^ --interactive复制代码

3、找到需要修改的 commit ,将首行的 pick 改成 edit
4、开始着手解决你的 bug
5、 git add 将改动文件添加到暂存
6、 git commit –amend 追加改动到提交
7、git rebase –continue 移动 HEAD 回最新的 commit
8、恢复之前的工作状态

git stash pop复制代码

大功告成,是不是想把整个 Commit 都修改一遍,逃~

此处参考自:修改 Commit 日志和内容

五、项目中使用

这时候问题又来了,为什么我提交的时候会有警告,这个又是如何做到的呢?

这时候,我们需要一款 Node 插件 validate-commit-msg 来检查项目中 Commit message 是否规范。

1.首先,安装插件:

npm install --save-dev validate-commit-msg复制代码

2.使用方式一,建立 .vcmrc 文件:

{
  "types": ["feat", "fix", "docs", "style", "refactor", "perf", "test", "build", "ci", "chore", "revert"],
  "scope": {
    "required": false,
    "allowed": ["*"],
    "validate": false,
    "multiple": false
  },
  "warnOnFail": false,
  "maxSubjectLength": 100,
  "subjectPattern": ".+",
  "subjectPatternErrorMsg": "subject does not match subject pattern!",
  "helpMessage": "",
  "autoFix": false
}复制代码

3.使用方式二:写入 package.json

{
  "config": {
    "validate-commit-msg": {
      /* your config here */
    }
  }
}复制代码

4.可是我们如果想自动使用 ghooks 钩子函数呢?

{"config": {
    "ghooks": {
      "pre-commit": "gulp lint",
      "commit-msg": "validate-commit-msg",
      "pre-push": "make test",
      "post-merge": "npm install",
      "post-rewrite": "npm install",}
  }}复制代码

ghooks 中我们可以做很多事情,当然不只是 validate-commit-msg 哦。

更多细节请参考:validate-commit-msg

六、Commit 规范的作用

1.提供更多的信息,方便排查与回退;
2.过滤关键字,迅速定位;
3.方便生成文档;

七、生成 Change log

正如上文提到的生成文档,如果我们的提交都按照规范的话,那就很简单了。生成的文档包括以下三个部分:

  • New features
  • Bug fixes
  • Breaking changes.

每个部分都会罗列相关的 commit ,并且有指向这些 commit 的链接。当然,生成的文档允许手动修改,所以发布前,你还可以添加其他内容。

这里需要使用工具 Conventional Changelog 生成 Change log

npm install -g conventional-changelog
cd jartto-domo
conventional-changelog -p angular -i CHANGELOG.md -w复制代码

为了方便使用,可以将其写入 package.jsonscripts 字段。

{
  "scripts": {
    "changelog": "conventional-changelog -p angular -i CHANGELOG.md -w -r 0"
  }
}复制代码

这样,使用起来就很简单了:

npm run changelog复制代码

到这里,我们所有的问题都搞明白了,🍻Cheers~

扩展:git rebase的用法

www.jianshu.com/p/4a8f4af4e…
git 命令大全:www.bookstack.cn/read/git-tu…

1.合并多个commit为一个完整commit

git rebase -i --root    // 从根开始,根的commit也可以修改
git rebase -i  [startpoint]  [endpoint]

其中-i的意思是--interactive,即弹出交互式的界面让用户编辑完成合并操作,[startpoint] [endpoint]则指定了一个编辑区间,如果不指定[endpoint],则该区间的终点默认是当前分支HEAD所指向的commit(注:该区间指定的是一个前开后闭的区间)。

image.png

image.png

git rebase -i 36224db

git rebase -i HEAD~3 
  • pick:保留该commit(缩写:p)

  • reword:保留该commit,但我需要修改该commit的注释(缩写:r)

  • edit:保留该commit, 但我要停下来修改该提交(不仅仅修改注释)(缩写:e)

  • squash:将该commit和前一个commit合并(缩写:s)

  • fixup:将该commit和前一个commit合并,但我不要保留该提交的注释信息(缩写:f)

  • exec:执行shell命令(缩写:x)

  • drop:我要丢弃该commit(缩写:d)

2.将某一段commit粘贴到另一个分支上

image.png

 git rebase   [startpoint]   [endpoint]  --onto  [branchName]
git  rebase   90bc0045b^   5de0da9f2   --onto master

其中,[startpoint] [endpoint]仍然和上一个命令一样指定了一个编辑区间(前开后闭),--onto的意思是要将该指定的提交复制到哪个分支上。
所以,在找到C(90bc0045b)和E(5de0da9f2)的提交id后,我们运行以下命令:

注:因为[startpoint] [endpoint]指定的是一个前开后闭的区间,为了让这个区间包含C提交,我们将区间起始点向后退了一步。
运行完成后查看当前分支的日志:

image.png

可以看到,C~E部分的提交内容已经复制到了G的后面了,大功告成?NO!我们看一下当前分支的状态:

image.png

当前HEAD处于游离状态,实际上,此时所有分支的状态应该是这样:

image.png 所以,虽然此时HEAD所指向的内容正是我们所需要的,但是master分支是没有任何变化的,git只是将C~E部分的提交内容复制一份粘贴到了master所指向的提交后面,我们需要做的就是将master所指向的提交id设置为当前HEAD所指向的提交id就可以了,即:

      git checkout master
      git reset --hard  0c72e64

image.png

此时我们才大功告成!

rebase 工作原理

准备工作

  • git 客户端安装(只要git bash即可)
  • github上新建一个项目

image.png

  • 项目clone到本地

image.png clone完成以后进入该项目文件夹下,准备工作完毕

模拟日常开发

同学A:

  • 执行git log image.png

可以看出此时该项目仅有一次提交记录

  • 执行新增文件a.txt,并本地提交一次后再次执行git log

image.png

这个时候打开github,刷新该项目的commit记录

发现远程仓库还是只有一次提交记录的,说明A同学还没有将自己最新的修改push到远程仓库,其他同学这个时候是看不到A的最新提交的

  • A同学将自己的最新提交Push到远程仓库 image.png

  • 再次刷新github 提交记录,发现已经多了一个A同学提交的最新记录了 image.png

切分支开发

  • 基于已有两次提交记录的本地master分支检出一个新分支dev,并将该分支推到远程仓库 image.png

  • 查看远程仓库,多了一个dev分支 image.png

  • 此时本地的git分支图类似如下 image.png

A同学开发功能

假设A同学基于dev分支开发功能,在本地新做了三次代码提交,git log
如下 image.png

那么此时的git分支图如下 image.png

  • 重点

如果此时在A同学准备进行第四次本地提交之前,另一个同学B向远程仓库推送了一个master分支的提交,即此时master实际的提交已经向前走了

我们这个时候在github上操作一次commit,模拟另一个同学此时push了master分支 image.png

  • A同学本地更新一下master分支

image.png

发现master分支已经向前走了一次提交,此时的分支图如下

image.png

此时我们知道A同学开发的dev分支是基于C2提交点切出来的,而这个时候master分支已经被更新了

如果A同学开发完毕,需要将其所作的功能合并到master分支 ,他可以有两种选择

直接git merge

如果A同学选择用git merge的方式进行合并dev到master分支,那么git会这么做

  1. 找出dev分支和master分支的最近共同祖先commit点,即C2
  2. 将dev最新一次commit(C5)和master最新一次commit(C6)合并后生成一个新的commit(C7),有冲突的话需要解决冲突
  3. 将以上两个分支dev和master上的所有提交点(从C2以后的)按照提交时间的先后顺序进行依次放到master分支上(提交记录就会有这些按照时间顺序排列的提交,并且多一次c7的合并记录,有待验证) image.png

git rebase 后再git merge

  1. rebase之前需要经master分支拉到最新
  2. 切换分支到需要rebase的分支,这里是dev分支
  3. 执行git rebase master,有冲突就解决冲突,解决后直接git add . 再git rebase --continue即可

此时的git log如下 image.png 可以发现其一并没有多出一次commit,其二dev后面几次提交的commit hash值已经变了,包括C3,C4,C5(这时候的C3,C4,C5 属于游离的状态,等待合并?有待验证)

  1. 切换到master分支,执行git merge dev image.png

发现采用rebase的方式进行分支合并,整个master分支并没有多出一个新的commit,原来dev分支上的那几次(C3,C4,C5)commit在rebase之后其hash值发生了变化,不在是当初在dev分支上提交的时候的hash值了,但是提交的内容被全部复制保留了,并且整个master分支的commit记录呈线性记录

其分支图最终如下 image.png

总结

  • git merge 操作合并分支会让两个分支的每一次提交都按照提交时间(并不是push时间)排序,并且会将两个分支的最新一次commit点进行合并成一个新的commit,最终的分支树呈现非整条线性直线的形式
  • git rebase操作实际上是将当前执行rebase分支的所有基于原分支提交点之后的commit打散成一个一个的patch,并重新生成一个新的commit hash值,再次基于原分支目前最新的commit点上进行提交,并不根据两个分支上实际的每次提交的时间点排序,rebase完成后,切到基分支进行合并另一个分支时也不会生成一个新的commit点,可以保持整个分支树的完美线性

另外值得一提的是,当我们开发一个功能时,可能会在本地有无数次commit,而你实际上在你的master分支上只想显示每一个功能测试完成后的一次完整提交记录就好了,其他的提交记录并不想将来全部保留在你的master分支上,那么rebase将会是一个好的选择,他可以在rebase时将本地多次的commit合并成一个commit,还可以修改commit的描述等

最后

如果你想要你的分支树呈现简洁,不罗嗦,线性的commit记录,那就采用rebase

参考文章:www.jianshu.com/p/6960811ac…