給你的git commit加點料

在工作中,我們通常使用git來管理代碼,當我們對代碼進行某項改動後,都可以通過git commit對代碼進行提交。git規定提交時必須要寫提交信息,作爲改動說明,保存在commit歷史中,方便回溯。但你仔細研究過git commit嗎?或者換句話說你注意過應該怎樣寫commit信息和優化commit信息嗎?本文將回答這些問題,希望對大家有所幫助。如有錯誤,請不吝指正。

一、git commit的意義

爲了對每次提交進行提交說明,方便之後回溯和團隊協作,Git 每次提交代碼,都要寫 Commit message(提交說明),否則就不允許提交。當參與團隊協作時,我們共同維護一個maste或dev的代碼,如果其他人改動了倉庫代碼,我們肯定想知道他到底改動了什麼,這次改動會對我們有什麼影響。怎麼看呢,直接看代碼肯定沒錯,但不是最好的方式。其實最方便的方式就是查看提交歷史了。如果提交信息寫的足夠好的話,不需要看代碼我們也清楚改動了什麼,這樣就可以提高協作效率和溝通成本了。

試想誰不想看到下面這樣優雅清晰明瞭提交歷史呢,如果再能自動生成ChangeLog文檔自然是極好的。我想沒有哪個程序員能抵擋得住這種誘惑吧。

在這裏插入圖片描述
在這裏插入圖片描述

總體來說,規範 commit信息的意義如下:

  • 可讀性即提供本次代碼改動的信息,方便其他人瀏覽和理解
  • 可回溯性即方便查找特定提交說明,回溯某些commit(比如新增特性、修復bug等)
  • 自動化性即可根據commit信息自動生成ChangeLog(改動說明)

鑑於此,我們需要寫出清晰規範的git commit。那麼什麼的規範的commit信息呢,請繼續看下去。

二、git commit 規範

爲引導用戶爲開源社區做貢獻,angular較早提出了進行pr時的commit說明規範(詳見Git Commit Guidelines),後被很多開源項目所接受,形成了一套約定式提交規範。首先讓我們從一條提交記錄出發看一下angular是如何寫提交信息的。
在這裏插入圖片描述

如上所示,angular提交信息遵循如下格式:

<type>(<scope>): <subject> # header信息頭必須
<BLANK LINE>
<body> # 信息體
<BLANK LINE>
<footer> # 結束部分
  • header 信息頭(必須)<type>(<scope>): <subject>

    信息頭必須要有type,它嚴格限定爲如下值:

    feat: A new feature
    fix: A bug fix
    docs: Documentation only changes
    style: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)
    refactor: A code change that neither fixes a bug nor adds a feature
    perf: A code change that improves performance
    test: Adding missing or correcting existing tests
    chore: Changes to the build process or auxiliary tools and libraries such as documentation generation
    

    另外如果本次提交是回退之前的提交,應該以revert:開頭,跟着要回退提交的header信息,在body中寫:This reverts commit <hash>

    其他開源項目的type類型也類似,像electron的type類型如下所示,詳見Commit message guidelines

    fix: A bug fix
    feat: A new feature
    docs: Documentation changes
    test: Adding missing tests or correcting existing tests
    build: Changes that affect the build system
    ci: Changes to our CI configuration files and scripts
    perf: A code change that improves performance
    refactor: A code change that neither fixes a bug nor adds a feature
    style: Changes that do not affect the meaning of the code (linting)
    vendor: Bumping a dependency like libchromiumcontent or node
    

    接下來是scope。它指定了本次提交的影響範圍,比如數據層、控制層、視圖層等等,視項目不同而定。如果影響範圍較大,可以使用通配符*。scope可以忽略。

    最後是subject,指出本次提交的主題,是對本次提交的簡短描述,不超過50個字符。注意它和前面的冒號之間要有空格,和type一樣,必須要有。subject內容有以下規定:

    1. 以動詞開頭,使用第一人稱現在時,比如change,而不是changed或changes
    2. 首字母小寫
    3. 結尾不加句號(.)

    type和subject決定了本次提交信息的質量,是靈魂所在。

  • body 信息體(可忽略)

    是對header中subject的補充,可以寫一些本次提交改動了哪裏和有什麼影響等內容。注意時態同樣要使用一般現在時,比如改動要寫change,不能用changed或changes。

  • footer 信息尾部(可忽略)

    尾部可以包含一些類似做出了哪些重大改變這樣的內容,也可以寫關閉了那些issue。另外Breaking Changes要以BREAKING CHANGE:打頭,後面跟空格或兩個新行和具體內容。關閉了那些bug要使用Closes打頭,哪怕只關閉了一個,例如Closes #234

angular關於commit message的詳細規範請參見Git Commit Message Conventions文檔

三、git commit的優秀輔助工具

看來要寫出符合git commit規範的信息也是不容易的,不過社區也爲我們提供了一些輔助工具來幫助進行提交,下面根據使用目的來簡單介紹一下這些工具。

1、生成commit信息

cz-cli 是一款可以交互式建立提交信息的工具。它幫助我們從type開始一步步建立提交信息,具體效果如圖所示,通過上下鍵控制指向你想要的type類型。
在這裏插入圖片描述

注意:

  • windows下使用git bash 運行該工具不能上下選擇,可以使用powershell代替。
  • 此工具要配合adapter(類似模板)使用,來提供交互提交的樣式包括type類型和提交的結構,可自定義。

全局環境下安裝使用方法如下:

# 需要同時安裝commitizen和cz-conventional-changelog,後者是adapter
$ npm install -g commitizen cz-conventional-changelog
# 配置安裝的adapter 
$ echo '{ "path": "cz-conventional-changelog" }' > ~/.czrc
# 使用
$ git cz

本地環境下(非全局安裝)使用方法如下:

# 安裝commitizen
$ npm install --save-dev commitizen
# 接下來安裝適配器
# for npm >= 5.2
$ npx commitizen init cz-conventional-changelog --save-dev --save-exact
# for npm < 5.2
$ ./node_modules/.bin/commitizen init cz-conventional-changelog --save-dev --save-exact 

// package.json script字段中添加commit命令
"scripts": {
   "commit": "git-cz"
}
// use
$ npm run commit

// 如果不希望每次使用npm run commit寫提交信息的話,可以使用git hook,把git commit命令替換成交互式提交,注意windows下不生效
"husky": {
  "hooks": {
    "prepare-commit-msg": "exec < /dev/tty && git cz --hook",
  }
}

2、校驗commit信息

commitlint是一款提交信息校驗工具,原理是可用git hooks在真正進行git commit入庫操作前對信息進行驗證,不符合規則的commit信息將會被阻止提交入庫,如下所示。
在這裏插入圖片描述

使用方式也很簡單:

# 安裝 commitlint cli and conventional config
$ npm install --save-dev @commitlint/{config-conventional,cli}
# Windows下自帶命令行工具不會識別/{x,x},所以使用以下語句安裝
$ npm install --save-dev @commitlint/config-conventional @commitlint/cli

# 生成commitlint配置文件,這步是必須的,不然提交會報錯,達不到效果
$ echo "module.exports = {extends: ['@commitlint/config-conventional']}" > commitlint.config.js

注意:使用commitlint時需要建立校驗文件commitlint.config.js,不然會校驗失敗

如果大家自己寫過git hooks,那麼可以把commitlint添加到git commit前的校驗中,如果不是很熟的話可以藉助一些工具,比如husky。

# 安裝husky
$ npm i -D husky
# package.json文件添加如下字段
"husky": {
  "hooks": {
    "commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
  }  
}

當然如果認爲遵守規則很累,可以在git commit時使用 --no-verify參數跳過驗證,直接提交。不過這樣使用該插件就沒意義了。所以儘量保證自己寫的commit信息符合規範。

3、生成changelog文檔

changelog是改動日誌,包含我們發佈的版本信息,以及每次版本更新的東西。

conventional-changelog-cli是一款changelog生成工具,通過提取commit信息(默認提取類型爲feat、fix和包含breaking change的提交信息)爲我們自動化生成changelog文檔。

全局使用方法如下:

# 安裝conventional-changelog-cli
$ npm install -g conventional-changelog-cli
$ cd my-project
# 保留原changelog文檔,生成本次改變
$ conventional-changelog -p angular -i CHANGELOG.md -s
# 完全重新生成changelog文檔
$ conventional-changelog -p angular -i CHANGELOG.md -s -r 0

如果不想全局安裝的話,可以使用npm run script方式運行,如下所示

$ npm i -D conventional-changelog-cli

// package.json script字段中添加version命令
"script": {
  "version": "conventional-changelog -p angular -i CHANGELOG.md -s && git add CHANGELOG.md"
}
// use
$ npm run version

conventional-changelog-cli生成changelog是根據到目前爲止的所有提交信息同最新一次tag作比較得到的所有feature、bug、breaking change信息進行文檔的生成,所以要在每次版本更新並生成changelog文檔後對分支打tag,再push到遠程分支。

這裏摘取conventional-changelog-cli推薦的整體流程:

  • Make changes 改動代碼
  • Commit those changes 使用git commit提交改動信息
  • Make sure Travis turns green 確保提交信息都符合規則
  • Bump version in package.json 更改package.json中的版本
  • conventionalChangelog 使用該工具生成changelog
  • Commit package.json and CHANGELOG.md files 提交package.json和changelog文檔
  • Tag 給分支打標籤
  • Push 推送到遠程分支

4、高級工具

社區同樣提供了一些高級工具來自動化進行上述的某些流程,可以說提供了一條龍服務。具體工具如下:

standard-version: 更新版本、生成changelog、打tag
semantic-release: 更新版本、生成changelog、打tag、推送代碼發佈

具體使用方法請自行查看文檔,這裏不再贅述。

四、修改及美化commit信息

前面講了這麼多git commit規範,可能大家要問了,我也準備從現在開始寫符合規範的提交信息,那之前項目中的不符合規範的信息怎麼辦呢?其實之前的提交信息也是有辦法修改的,關鍵就是使用git rebase命令。此外還有一個問題就是我寫了很多條提交信息,但其實都是乾的同一件事(這很常見,比如說要修復某個bug,改好之後發現這個bug在其他設備上並沒有改好,於是又改好之後提交了一次,但都是改的同一個問題),有辦法將他們合併成一條嗎?其實有的,關鍵也在於git rebase。是不是瞬間感覺這個命令很強大,想要迫不及待的試一下呢。

好吧,不賣關子了,直接來到正題,如何使用git rebase進行變基操作。先說一下如果直接使用git rebase [target-branch],雖然可以幫我們在本分支基於目標分支進行變基操作,但它只會讓兩個分支的所有提交連成一條線,不能修改具體提交信息,所以需要使用git rebase -i進行交互式操作。首先了解一下git rebase -i的幾個操作。

在這裏插入圖片描述
如上圖所示,git rebase提供了幾個操作來對提交信息進行處理,這裏挑幾個常用的介紹一下。

  • pick 縮寫爲p,保留本次提交包括文件改動和提交信息
  • reword 縮寫爲r,保留本次改動,但重寫提交信息
  • edit 縮寫爲e,保留本次改動,而且可以附加改動、編輯提交信息
  • squash 縮寫爲s,保留改動,但本次提交會附加到上次提交中,也就是和上次提交合並(提交信息也會合並)
  • fixup 縮寫爲f,同squash,不同的是會丟棄掉本次提交的說明信息
  • drop 縮寫爲d,丟掉本次提交(包括文件改動和提交信息)

另外還要知道git rebase --continue命令用來告訴git繼續進行合併,一般在解決衝突後使用(一般來講如果所有的提交沒有衝突的話,可以一直走下去,直到出現成功的提示。但如果兩次提交合並涉及到衝突,就需要解決衝突並在git add後使用該命令繼續走下去);git rebase --abort命令用來終止變基操作,一般在不想進行rebase時使用(大部分時間是因爲玩脫了,rebase到了一種不可預知的地步,不知道幹啥了,就只能終止了)。直到出現類似“Successfully rebased and updated refs/heads/dev.”的消息就說明本次變基成功了。下面具體講一下如何修改和美化提交信息。

1、修改commit信息

修改提交信息可以分爲以下幾個步驟,當然具體步驟還要具體情況具體分析,有些可以忽略。

  1. 暫存工作狀態:使用git stash將當前工作狀態進行暫存,如果沒有需要暫存的工作狀態請忽略

  2. 轉到需要修改的提交上:使用git rebase -i [commit-id]^將 HEAD 移動到需要修改的 commit-id 上,因爲該[commit-id]也在修改之列,所以要基於它的上一個提交變基,或者直接使用[commit-id]^

  3. 將pick修改爲想要進行的操作命令:進行編輯時一般要把所有需要修改的commit的pick改爲r或e,提示:如果多的話可以使用vim的全局替換

  4. 重新編輯commit信息

  5. 附加修改:如果需要在該分支上修改其他內容,可以在改動文件後直接使用git add 將其添加到暫存區,並使用 git commit –amend 追加改動到提交。如果不需要請忽略

  6. 繼續進行變基操作:使用git rebase –continue繼續,一般如果提交沒有衝突或者附加修改的話,不需要手動輸入,git會自動進行下一次操作。

  7. 變基完成:重複以上步驟,直到出現變基成功文字。如果暫存過工作狀態,需要使用git stash pop恢復之前的工作狀態,結束。

2、commit信息的美化

我們大多數時候只會使用git merge來將開發代碼合併到主分支。不過這樣會導致主分支包含我們合併分支的所有提交信息。長此以往,會讓commit history越來越繁雜,但其實合併的代碼完全可以用一條提交信息比如feat: 添加某項新特性來代替。當然還有其他原因會導致commit信息重複且不必要。比如當我們在拿不準bug到底有沒有修復成果時可能會多次改動代碼進行測試,如果每次改動測試都進行了提交,就會產生多次commit信息,如果不對這些commit做合併的話,在我們將代碼合併到開發主分支時就會產生很多繁雜的commit信息,不過由於這些commit信息都是爲解決同樣的事情服務的,完全可以合到一起。所以如果你嫌棄commit信息太囉嗦冗餘,那麼就需要對其進行美化了。美化主要有兩種方式:git rebasegit merge --squash,這裏將介紹它們的使用,並進行對比。

  1. rebase merge

    注意:如果要合併的commit歷史過多並且對git rebase命令使用不熟練,請慎用,不然可能會被各種衝突逼瘋的!

    如果我們想將dev的提交信息進行美化併合併到master分支,可以進行如下操作:

    • 先切換到 dev 分支:git checkout dev
    • 基於master進行變基操作:git rebase -i master,其中具體的操作請參見上一小節,不同的不過是把一些提交的pick改爲s而已,即提交信息合併到上一次提交。
    • 切換回目標分支:git checkout master
    • 合併: git merge dev
  2. squash merge

    同樣以dev分支合併到master爲例:

    • 切換到目標分支:git checkout master
    • 以 squash 的形式 merge:git merge --squash dev
    • 提交:修改衝突後進行提交

兩種方式都能實現提交信息的合併,保持 master 分支幹淨整潔。不過它們也有很大不同。

squash merge會把分支的所有提交一股腦應用在目標分支上,然後你要修改衝突後再提交,生成一次commit,而這次commit包含dev的所有改動。產生的這次新的commit的作者自然也就是做了本次將dev分支合併到master分支操作的人,他可能並不是原dev分支的作者,也就掩蓋了真實的作者。此外無論改動有多少,只會產生一次提交記錄。

rebase merge擁有更高的可操作性,它允許我們可以選擇保留幾條有意義的提交,而不像squash merge只有一條。當然有更高的操作自由度也意味着步驟比較繁瑣,不像squash merge簡單方便。當然變基操作完成後在主分支上使用git merge進行合併,原作者信息會得到保留。

總之,如果你想要更快進行提交合並,能容忍原作者信息丟失的話,可以選擇squash merge;如果你想要保留多條有意義的提交信息和需要保留原作者信息的話,rebase merge可能要更適合你。

話說回來,其實git rebase命令非常強大,不僅能做本分支commit信息的修改、合併,還能使用git rebase [startpoint] [endpoint] --onto [branchName]將本分支的某一段commit集合合併到另一分支。嗯,git很是博大精深呢,要想達到精通,我還有很長一段路要走!

參考資料:

  1. 你可能會忽略的 Git 提交規範
  2. Commit message 和 Change log 編寫指南
  3. Git Commit Message Conventions
  4. 【Git】rebase 用法小結
  5. merge squash 和 merge rebase 區別
  6. git筆記-進階
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章