輕鬆上手約定式提交:Commitizen 初體驗

最近參加 DevOps Meetup 的活動,Speaker 提到團隊使用 Commitizen 這套工具來統一提交訊息的風格,當場就有種醍醐灌頂的感覺。我們團隊前陣子才 Suffer 在 Commit Style 分歧,知識管理效率低落,有時只有 commit 本人能看懂,而使用共同模板又太浪費時間,跟 Git 鼓勵 commit 的精神背道而馳。聽完後,真的是相見恨晚,完全命中我的痛點。

Commitizen 是由 AngularJS 的規範衍伸而來,各團隊可以依照需求自行調整,我們 Step by Step 來看看 Commitizen 的效果如何。

Install Node.js

因為 Commitizen 是使用 Node.js 開發,不免俗的,要裝一下 Node.js,Ubuntu 的安裝方式是

sudo apt install nodejs
sudo apt install npm

確認是否有安裝完成

ken@ken-Lenovo-ideapad-330-15ICH:~/git/git$ node -v
v8.10.0
ken@ken-Lenovo-ideapad-330-15ICH:~/git/git$ npm -v
3.5.2

Install Commitizen

接著使用 Node.js 的 Package Management 工具 npm 來安裝 Commitizen

npm install -g commitizen
echo '{ "path": "cz-conventional-changelog" }' > ~/.czrc

-g 是全域安裝的意思,如果沒有需要針對 Project 制定 Style,用全域安裝即可,.czrc 則是用來設定 template 的路徑。

Git Format

在預設的 format 中,commit comment 由三個部分組成

<head>
<body>
<footer>

讓我們看一個簡單的例子

commit 4030e040b6044de68b2750702a5b6065c887960c
Author: kenwschen <ken*****@[gmail.com](mailto:[email protected])>
Date:   Thu Nov 28 22:51:44 2019 +0800

feat(libhello): add hello file

hello, this is a longer description

fix #100

第一行是 head,也就是 title,通常會由

<type>(<scope>): <subject>

在本例中可以看成這個 commit 為新增功能(feature),更改的 module 是 libhello,簡單描述是 add hello file。

中間行是 body,代表詳細的描述,通常會說明要解決的問題是什麼,具體做法是什麼等等。

最後一行是 footer,通常會標明相關的 issue,如果沒有將 issue 跟 git 結合在一起的話,footer 可以不標。

Git cz

實際執行 Commitizen,使用 git cz 來取代 git commit

可以看到,Commitizen 會很貼心顯示選單讓 user 選擇,只要照著問題跟選單將答案填入就好,就是這麼簡單。

來看一下提交的結果

ken@ken-Lenovo-ideapad-330-15ICH:~/git$ echo hello > hello     
ken@ken-Lenovo-ideapad-330-15ICH:~/git$ git add .              
ken@ken-Lenovo-ideapad-330-15ICH:~/git$ git cz                 
[email protected], [email protected]                  
                                                                
? Select the type of change that you're committing: feat:        A new feature                                                     
? What is the scope of this change (e.g. component or file name): (press enter to skip) libhello                                   
? Write a short, imperative tense description of the change (max 84 chars):                                                        
    (14) add hello file                                               
? Provide a longer description of the change: (press enter to skip)
    hello, this is a longer description
? Are there any breaking changes? No
? Does this change affect any open issues? No
[master 2e1ea38] feat(libhello): add hello file
    1 file changed, 1 insertion(+)
    create mode 100644 hello

ken@ken-Lenovo-ideapad-330-15ICH:~/git$ git log
commit 2e1ea3868dcf972c2499378ee9d5b3ac7ab654b6 (HEAD -> master)
Author: kenwschen <[ken*****@gmail.com](mailto:[email protected])>
Date:   Fri Nov 29 00:40:31 2019 +0800

feat(libhello): add hello file

hello, this is a longer description

是不是太美了!

Customize Format

如果專案有自訂格式,例如需要標註修改方式、修改目的等等,可以怎麼做?Commitizen 支援許多模板,其中 cz-customizable 有讓 user 自訂選項的彈性,先安裝起來

sudo npm install -g cz-customizable
echo '{ "path": "cz-customizable" }' > ~/.czrc

將配置項的範例複製到家目錄

cp /usr/local/lib/node_modules/cz-customizable/cz-config-EXAMPLE.js ~/.cz-config.js

打開配置文件,可以看到其中有許多配置設定,假設現在需要新增一個互動問答,讓 user 輸入 commit 的 purpose,可以在其中加入

    messages: {
        type: "Select the type of change that you're committing:",
        scope: '\nDenote the SCOPE of this change (optional):',
        customScope: 'Denote the SCOPE of this change:',
        subject: 'Write a SHORT, IMPERATIVE tense description of the change:\n',
        body: 'Provide a LONGER description of the change (optional). Use "|" to break new line:\n',
        **bodyPurpose: 'The purpose of the change:\n',**
        breaking: 'List any BREAKING CHANGES (optional):\n',
        footer: 'List any ISSUES CLOSED by this change (optional). E.g.: #31, #34:\n',
        confirmCommit: 'Are you sure you want to proceed with the commit above?',
      },

其中 bodyPurpose 這行是新加入的選項。

接著修改問句文件

sudo vi /usr/local/lib/node_modules/cz-customizable/questions.js

在其中加入 bodyPurpose

    ...
    messages.body =
          messages.body || 'Provide a LONGER description of the change (optional). Use "|" to break new line:\n';
        **messages.bodyPurpose = messages.bodyPurpose || 'The purpose of the change:\n';**
        messages.breaking = messages.breaking || 'List any BREAKING CHANGES (optional):\n';
    ...

    ...
          {
            type: 'input',
            name: 'body',
            message: messages.body,
          },
          **{
            type: 'input',
            name: 'bodyPurpose',
            message: messages.bodyPurpose,
          },**
    ...

然後修改 commit 生成文件,將 bodyPurpose 加入

    let body = wrap(answers.body, wrapOptions) || '';
    **body = body + (wrap(answers.bodyPurpose, wrapOptions) || '');**
    body = addBreaklinesIfNeeded(body, config.breaklineChar);

現在來看一下修改的成果

而實際的 log 是

commit 3cc8c9aa4a9084fb4b8faa651d98b5376f24e4d6 (HEAD -> master)
Author: kenwschen <[[email protected]](mailto:[email protected])>
Date:   Fri Nov 29 19:56:58 2019 +0800

feat(libhello): add hello file

add a new file to test commitizen tool. hello is a lib that can say "hello"

小結

有時候程式寫一寫,會忘記升級自己的工具,很多時候團隊遇到的問題不是人的問題,而是工具的問題,我們應該要盡量 align 目標,用工具時時提醒出發點在哪。commit comment 原本是為了溝通而存在,而 Commitizen 可以幫我們更好地去做這件事。

Reference

Read more

OAuth 2.0 的身份認證:OpenID Connect

OAuth 2.0 的身份認證:OpenID Connect

OAuth 2 讓網路服務可以存取第三方的受保護資源,因此,有些開發者會進一步利用 OAuth 2 來進行使用者認證。但這中間存在著一些語義落差,因為 OAuth 2 當初設計目的是「授權」而不是「認證」,兩者關注的焦點會有些不同。OpenID Connect 是基於 OAuth 2 的一套身份認證協定,讓開發者可以在 OAuth 2 授權的基礎上,再加入標準的認證流程。在這篇文章中,我會說明授權跟認證的場景有何差異,並講解 OpenID Connect 如何滿足認證需求。 因為 OpenID Connect 是建構在 OAuth 2 的基礎上,我會假設這篇文章的讀者已經知道 OAuth 2 的組件與流程,如果你不熟悉,可以先閱讀另外兩篇文章 * OAuth 2.0:

By Ken Chen
更好的選擇?用 JWT 取代 Session 的風險

更好的選擇?用 JWT 取代 Session 的風險

因為 HTTP 是無狀態協定,為了保持使用者狀態,需要後端實作 Session 管理機制。在早期方式中,使用者狀態會跟 HTTP 的 Cookie 綁定,等到有需要的時候,例如驗證身份,就能使用 Cookie 內的資訊搭配後端 Session 來進行。但自從 JWT 出現後,使用者資訊可以編碼在 JWT 內,也開始有人用它來管理使用者身份。前些日子跟公司的資安團隊討論,發現 JWT 用來管理身份認證會有些風險。在這篇文章中,我會比較原本的 Session 管理跟 JWT 的差異,並說明可能的風險所在。 Session 管理 Session 是什麼意思?為什麼需要管理?我們可以從 HTTP 無狀態的特性聊起。所謂的無狀態,翻譯成白話,就是後面請求不會受前面請求的影響。想像現在有個朋友跟你借錢,

By Ken Chen

Goroutine 的併發治理:掌握生命週期

從併發的角度來看,Goroutine 跟 Thread 的概念很類似,都是將任務交給一個執行單元來處理。然而不同的是,Goroutine 將調度放在用戶態,因此更加輕量,也能避免多餘的 Context Switch。我們可以說,Go 的併發處理是由語言原生支援,有著更好的開發者體驗,但也因此更容易忘記底層仍存在著輕量成本,當這些成本積沙成塔,就會造成 Out of Memory。這篇文章會從 Goroutine 的生命週期切入,試著說明在併發的情境中,應該如何保持 Goroutine 的正常運作。 因為這篇講的內容會比較底層,如果對應用情境不熟的人,建議先看過同系列 * Goroutine 的併發治理:由錯誤處理談起 * Goroutine 的併發治理:值是怎麼傳遞? * Goroutine 的併發治理:管理 Worker Pool 再回來看這篇,應該會更容易理解。 Goroutine 的資源使用量 讓我們看個最簡單的例子,假設現在同時開

By Ken Chen

Goroutine 的併發治理:管理 Worker Pool

併發會需要多個 Goroutine 來同時執行任務,Goroutine 雖然輕量,也還是有配置成本,如果每次新的任務進來,都需要重新建立並配置 Goroutine,一方面不容易管理 Goroutine 的記憶體,一方面也會消耗 CPU 的運算效能。這時 Worker Pool 就登場了,我們可以在執行前,先將 Goroutine 配置好放到資源池中,要用時再調用閒置資源來處理,藉此資源回收重複利用。這篇文章會從 0 開始建立 Work Pool,試著丟進不同的場景需求,看看如何實現。 基本的 Worker Pool Worker Pool 的概念可以用這張圖來解釋 Job 會放在 Queue 中送給 Pool 內配置好的 Worker,Worker 處理完後再將結果送到另一個 Queue 內。因為這是很常見的併發模式,

By Ken Chen