不會寫shell的程序員照樣是好前端——用Node.JS實現git hooks
來自: http://hao.jser.com/archive/10098/
前言
git hooks想必很多攻城獅都不陌生,官方對于hooks有詳細的 文檔 ,也有站內網友的文章 Git Hooks (1):介紹 , GIt Hooks (2):腳本分類 ,說的非常詳細了,這里就不多做介紹,這里主要介紹一下如何寫一個hook。
一個基本的git hook長什么樣?
對git-hooks有一個入門認識的朋友都知道,hooks存放在git倉庫的 .git/hooks 目錄下,其中包括很多hooks,這些是在git 倉庫創建的時候自動生成的,后綴名統一都是 .sample ,表示這些hooks都是默認不啟用的,當把后綴名去掉之后,就變成了可以使用的hook。
舉個栗子
pre-commit這個hook是在 git commit 的時候觸發的hook,這個hook里面寫了什么呢?代碼我就不貼了,沒啥勁,主要的幾點就是:
-
這是一個shell腳本
-
這個腳本運行了一些東西然后退出了
-
退出的時候退出的錯誤碼不是確定的
這就是一個hook的最基本的組成:在命令行執行git操作的時候,自動執行hooks目錄下相應的可執行腳本,然后根據腳本的退出狀態決定此次操作是否成功。當退出的錯誤碼不為0的時候,表示失敗,操作終止,否則操作繼續。
模擬場景
如果現在有這樣一個場景,在你的git倉庫里,要求不允許提交 dist 目錄,并且通過 mocha 的測試,否則不允許提交,用git hook 怎么做呢?
首先,這是在提交的時候的一個限制,所以應該考慮使用 pre-commit 這個hook,代碼就不寫了(不會寫shell… Orz),整個過程如下:
-
檢查是否有 dist 目錄,如果沒有的話下一步,否則退出,錯誤碼置為1。
-
執行mocha命令進行測試,如果測試全部通過的話,退出,錯誤碼為0,否則錯誤碼為1,同樣退出。
這樣,當上述任何一步沒有通過的時候,這個hook就會被終止,git-commit就無法通過,也就達到了限制提交的目的。
shell腳本的局限性——不會寫
作為一名普通的前端,兼,一名不太合格的工程師,我對于shell腳本實在是不熟悉,連Linux命令都玩不轉,別說寫出666的shell腳本了,囧~ 所以要另辟巧徑做這件事。
前端仔們對js應該是非常熟練的,所以如果能用js寫hooks,那不就爽了?而Node.JS正好給了我們希望,感激涕零的話就不多說了,絕對感動到哭!
Node.js寫起腳本來也非常簡單,比如一個最簡單的腳本
#!/usr/bin/env node console.log('Hello World!');
給腳本賦予可執行權限之后就完全可以當做shell腳本來跑了,麻麻再也不用擔心我不會shell了。同樣的,在hooks中我們也可以這樣用。再舉個栗子
還是剛才的場景,不允許有 dist 目錄,同時通過所有mocha測試,用Node就可以這樣寫(這次我能show出代碼了)
#!/usr/bin/env node var fs = require('fs'), spawnSync = require('child_process').spawnSync; if(fs.existsSync('./dist')){ console.log('Commit Abort!Please remove dist directory.'); process.exit(1); } // 使用同步方法spawnSync執行mocha,測試的結果在result.status中,通過為0,不通過為1 var result = spawnSync('./node_modules/.bin/mocha',['test']); if(result.status){ console.log('Commit Abort!Test failure.'); } process.exit(result.status);
這就是一個用Node.JS實現的基本的git-hook。
Node.JS的局限性——不能動
client-side hook的一個問題就是沒法在隨著倉庫變動,如果項目成員多的話,每個人都需要在自己本地添加一次,hooks有變動了更新也比較麻煩。
解決方案
我個人對這個問題有一個簡單解決方案,我做了一個倉庫 git-hooks-node ,每次寫好git hooks之后通過自己寫的工具進行build,生成一個類似于安裝器的文件,然后提交到遠程倉庫,如 pre-commit.js 是hook具體的內容, pre-commit.installer.js 是生成的安裝文件,也是一個腳本,github上的每一個文件都有相應的raw地址,如這個安裝文件的地址為 raw pre-commit.installer.js ,然后mac OS下的用戶就可以使用 curl 獲取腳本并運行,如下:
curl https://raw.githubusercontent.com/y8n/git-hooks-node/master/xgfe-ma/pre-commit.installer.js | node
安裝效果如下
這樣只要寫好一個hook并發布,項目成員只要知道地址就可以一鍵安轉(想想還有點小激動呢)。這樣雖然沒有解決hook不會隨著倉庫移動的問題,但也提供了一種在項目組里通用一套hook的方案。
其他解決方法
husky 是GitHub上一個開源項目,它的做法是在 npm install 這個模塊的時候自動在 .git/hooks 目錄下創建很多hooks,然后再在package.json中指定每一個hook的執行腳本,如下
"scripts": { "precommit": "npm test", "prepush": "npm test", "commit-msg": "./validate-commit-msg.js", "...": "..." }
這樣就可以把hooks隨著項目變動,真正做到項目成員共用一個git hook,但問題就是必須在項目中依賴husky,不過想想這樣的方法也比上面我的方法高明許多 -.-!