登录 log in
动态
DYNAMIC STATE
首页    >   动态   
>   工作中如何打造优雅的Git工作流和Commit规范!
HOME    >   DYNAMIC STATE   
>   工作中如何打造优雅的Git工作流和Commit规范!
刘建飞
浙江千仞网络股份有限公司
1701347650000

工作中如何打造优雅的Git工作流和Commit规范!

前言

🤓Git大家都非常熟悉了,就不做过多介绍,但是如何用好Git、如何进行合理的分支开发、Merge你是否有一个规范流程呢?💤

不论是一个团队一起开发一个项目,还是自己独立开发一个项目,都少不了要和Git打交道,这些都是作为开发者必须要掌握的。每个团队也许有自己的Git工作流,今天给你分享一个通用的流程和规范。

既然说到Git得先有个协同原则📜:

统一使用Git作为版本控制的主要工具。

统一使用GitFlow流程管理控制版本

📚 全文字数 : 5k

⏳ 阅读时长 : 6min

📢 关键词 : Git命令、工作流、分支规范、Commit Messages规范

基本命令操作

我们来看下git的基本操作,这些命令基本可以完成我们日常对代码版本开发合并需求

git add . :将变更从工作目录移至暂存区域

git commit -m "fix: xxx" :将暂存区中的文件提交到本地仓库中分支中

git pull:用于从远程获取代码并合并本地的版本

git push:用于从将本地的分支版本上传到远程并合并

这些操作命令在各个工作区、仓库之间如何进行流转的呢?如下图

git有好几个区,工作区(workspace)、暂存区(indexStage)、本地仓库(local repository)、还有远程仓库(remote repository)。

远程仓库为我们保存一份代码,如github,而工作区、暂存区和本地仓库都在本地

常用分支建议

前面简单讲了下代码提交流程,但是版本控制并不是简单代码提交就完事了,这里分享一些常用的分支开发,但并不是任何项目都一定按照这种分支结构来定义,按你的需求来确定需要哪些。比如你一个人开发一个小型项目,那么就不用搞的那么复杂,如果是多人协同开发版本迭代较快,那么下面的分支内容会让你开发节奏更清晰合理!

生产分支(master)‌

master分支是仓库的主分支,也有人叫production分支,这个分支包含最近发布到生产环境的代码,最近发布的release, 这个分支只能从其他分支合并,不能在这个分支直接修改‌

master 分支一般由release、develop以及hotfix分支合并,任何时间都不能直接修改代码

开发分支(develop)‌

这个分支是我们的主开发分支,始终保持最新完成以及bug修复后的代码.

包含所有要发布到下一个release的代码,这个主要合并与其他分支,比如feature分支‌

一般开发的新功能时,feature分支都是基于develop分支下创建的

补丁分支(hotfix)‌

当我们在生产环境发现新的Bug时候,我们需要基于master分支创建一个hotfix分支,然后在hotfix分支上修复bug

完成hotfix后,我们要把hotfix分支合并回master和develop分支‌,所以hotfix的改动会进入下一个release

发布分支(release)‌

当你需要发布一个新功能的时候,要基于develop分支创建一个release分支

在release分支做为基准进行测试并修复bug,完成release后,把release合并到master和develop分支‌

release 分支为预上线分支,发布提测阶段,会release分支代码为基准提测

功能分支(feature)‌

feature分支主要是用来开发一个新的功能,一旦开发完成,我们合并回develop分支,然后提交合并请求到 release 分支进行提测。

分支命名: feature/ 开头的为特性分支, 命名规则: feature/user_module、 feature/cart_module

Git工作流

上面那么多种分支类型,而且不同的分支又是基于其他分支,每次看完之后都记得,但是结合起来发现仅仅停留在有印象,是的,我们不好从单纯的文字上理清分支之间的关系和合并方式。

没关系,用个图把之间的关系梳理清楚:

其实核心是要弄明白主干和各个开发分支的关系,以及你的开发分支该和谁去合并。

不过还是那句话根据自己的项目和业务团队去指定Git工作,不能为了更风去创建并不适合团队的分支,不然会导致开发在无聊的敲命令,花费时间在各种分支的合并上,反而降低了效率。

适合别人的未必适合大家,互相交流,选择合适自己的!

更多方位的流程,大家可以看看这篇文章# 字节研发设施下的 Git 工作流

Commit编写规范

好的Commit messages 日志编写会带来极大的帮助,它也反映了一个开发人员是否是良好的协作者,更重要的是在后续修复bug和实现新的feture时,对于之前实现的功能、解决的bug可以一目了然,而不用通过阅读代码去了解曾经的实现,因为这会是意见非常痛苦的事情!

忘了说,你的Commit messages会是Code Review人员参考基本,你应该不想被无情的打回吧?😅😅😅

目前,社区有多种 Commit messages 的写法规范。来自Angular (前端框架)规范是目前使用最广的写法,比较合理和系统化。

其实浏览过Github开源项目的同学,如果有注意Pull requests的话就有了解,行我们看下Angular的提交规范到底是咋样的,为啥会成为参考标杆🚩🚩🚩。

Commit messages提交可以参照以下格式

<type>: <subject><BLANK LINE> 空白行<body><BLANK LINE> 空白行<footer>

  • type: 本次 commit 的类型,诸如 bugfix docs style 等

  • scope: 本次 commit 波及的范围

  • subject: 简明扼要的阐述下本次 commit 的主旨,在原文中特意强调了几点 :

    . 使用祈使句,是不是很熟悉又陌生的一个词,来传送门在此 祈使句

    . 首字母不要大写

    . 结尾无需添加标点

  • body: 同样使用祈使句,在主体内容中我们需要把本次 commit 详细的描述一下,比如此次变更的动机,如需换行,则使用 |

  • footer: 描述下与之关联的 issue 或 break change

Commit Type的类别

  • feat: 添加新特性

  • fix: 修复bug

  • docs: 仅仅修改了文档

  • style: 仅仅修改了空格、格式缩进、都好等等,不改变代码逻辑

  • refactor: 代码重构,没有加新功能或者修复bug

  • perf: 增加代码进行性能测试

  • test: 增加测试用例

  • chore: 改变构建流程、或者增加依赖库、工具等

Commit messages格式要求

标题行:50个字符以内,描述主要变更内容

主体内容:更详细的说明文本,建议72个字符以内。需要描述的信息包括:

  • 为什么这个变更是必须的? 它可能是用来修复一个bug,增加一个feature,提升性能、可靠性、稳定性等等

  • 他如何解决这个问题? 具体描述解决问题的步骤

  • 是否存在副作用、风险?

如果需要的化可以添加一个链接到issue地址或者其它文档

来看这个这位老哥在Angular项目详细的commit信息,大家可以对照上面的规范看,可以用一个词描述这种好的Commit方式【“赏心悦目”】,也许这也就是为什么这个项目的Commit会成为众多人模仿的原因吧!

关于名词简称

软件团队中经常需要Git进行团队协作开发,但是不少职场小宝朋友对于从大佬口中冒出来的一些字母缩略词一脸蒙蔽,比如MR、CR,这里一次说个明白,让你不再迷茫各种简称,哈哈!

这里总结了一些,

  • CR:Code Review. 请求代码审查。

  • PR:pull request. 拉取请求,给其他项目提交代码。

  • MR:merge request. 合并请求

  • ACK:Acknowledgement. 承认,同意。表示接受代码的改动。

  • TL;DR:Too Long; Didn't Read. 太长懒得看。常见于README文档。

  • WIP:Work In Progress. 进展中,主要针对改动较多的 PR,可以先提交部分,标题或 Tag 加上 WIP,表示尚未完成,这样别人可以先 review 已提交的部分。

链接:https://juejin.cn/post/7295304057655672842

(版权归原作者所有,侵删)

` if(dynamicsTypeOne==1){ if(dynamicsTitleOne){ let textBox = document.createElement('div') textBox.innerHTML = dynamicsTitleOne textBox.style.fontSize = '0.3rem' textBox.style.marginBottom = '0.3rem' textBox.style.textAlign = 'center' document.querySelector('.content').appendChild(textBox) } if(dynamicsCoverOne){ let videoBox = document.createElement('video') videoBox.src = imgHead(dynamicsCoverOne) videoBox.autoplay = true videoBox.loop = true videoBox.controls = true videoBox.style.width = '100%' videoBox.style.marginBottom = '0.2rem' document.querySelector('.content').appendChild(videoBox) } if(dynamicsDetailOne){ //好像是视频封面图 // dynamicsDetailOne = dynamicsDetailOne.split(',') // dynamicsDetailOne.forEach((item,value)=>{ // let imgBox = document.createElement('img') // imgBox.src = imgHead(item) // document.querySelector('.trends_detail_mainBox').appendChild(imgBox) // }) } }else if(dynamicsTypeOne==0) { console.log('00000000000123'); if(dynamicsTitleOne){ let textBox = document.createElement('div') textBox.innerHTML = dynamicsTitleOne textBox.style.fontSize = '0.3rem' textBox.style.marginBottom = '0.3rem' textBox.style.textAlign = 'center' document.querySelector('.content').appendChild(textBox) } if(dynamicsDetailOne){ if(dynamicsTitleOne){ let dynamicsDetail = JSON.parse(dynamicsDetailOne) dynamicsDetail.forEach(item=>{ if(item.type==1){ let textBox = document.createElement('div') textBox.innerHTML = item.value textBox.style.fontSize = '0.3rem' textBox.style.marginBottom = '0.3rem' textBox.style.textAlign = 'center' document.querySelector('.content').appendChild(textBox) } if(item.type==2){ let imgBox = document.createElement('img') imgBox.src = imgHead(item.value) document.querySelector('.content').appendChild(imgBox) } }) }else { let textBox = document.createElement('div') textBox.innerHTML = dynamicsDetailOne textBox.style.fontSize = '0.3rem' textBox.style.marginBottom = '0.3rem' textBox.style.textAlign = 'center' document.querySelector('.content').appendChild(textBox) } } if(dynamicsCoverOne){ dynamicsCoverOne = dynamicsCoverOne.split(',') dynamicsCoverOne.forEach((item,value)=>{ let imgBox = document.createElement('img') imgBox.src = imgHead(item) if(value == dynamicsCoverOne.length-1){ imgBox.style.marginBottom = '0.2rem' } document.querySelector('.content').appendChild(imgBox) }) } }else { if(dynamicsTitleOne && dynamicsTypeOne!=3){ let textBox = document.createElement('div') textBox.innerHTML = dynamicsTitleOne textBox.style.fontSize = '0.3rem' textBox.style.marginBottom = '0.3rem' textBox.style.textAlign = 'center' document.querySelector('.content').appendChild(textBox) } if(dynamicsCoverOne && dynamicsTypeOne!=3){ dynamicsCoverOne = dynamicsCoverOne.split(',') dynamicsCoverOne.forEach((item,value)=>{ let imgBox = document.createElement('img') imgBox.src = imgHead(item) if(value == dynamicsCoverOne.length-1){ imgBox.style.marginBottom = '0.2rem' } document.querySelector('.content').appendChild(imgBox) }) } if(dynamicsDetailOne){ let textBox = document.createElement('div') textBox.innerHTML = dynamicsDetailOne textBox.style.fontSize = '0.2rem' document.querySelector('.content').appendChild(textBox) } } let timeone = $(".middle_right").text() console.log(timeone,'timeone'); // 输出格式化后的日期字符串 if(timeone){ let timestamp = timeone.trim()*1; // 创建一个新的 Date 对象,并将时间戳作为参数传递 const date = new Date(timestamp); // 使用 Date 对象的方法来获取日期和时间 const year = date.getFullYear(); const month = date.getMonth() + 1; // 月份从 0 开始,所以要加 1 const day = date.getDate(); const hours = date.getHours(); const minutes = date.getMinutes(); const seconds = date.getSeconds(); console.log(year,'year'); console.log(month,'month'); console.log(day,'day'); console.log(hours,'hours'); console.log(minutes,'minutes'); console.log(seconds,'seconds'); // 格式化日期输出 const formattedDate = year+'-'+((month<10)?('0'+month):month)+'-'+((day<10)?('0'+day):day)+' '+((hours<10)?('0'+hours):hours)+':'+((minutes<10)?('0'+minutes):minutes); console.log(formattedDate); // 输出格式化后的日期字符串 let timetwo = '发布日期:'+formattedDate $(".middle_right").html(timetwo) }else{ $(".middle_right").html('') }