TLDR;前端
介紹 Git 鉤子的基本開發流程node
介紹如何用 Node.js 寫 Git 鉤子git
Git 鉤子是指在特定的 Git 動做(如:git commit
、 git push
)下被觸發的腳本。而鉤子主要被分爲兩種:shell
客戶端鉤子數據庫
服務端鉤子npm
而客戶端鉤子又被分爲如下幾種:安全
類型 | 鉤子名稱 | 接收參數 | 能否終止操做 |
---|---|---|---|
提交工做流鉤子 | pre-commit | \ | 是 |
提交工做流鉤子 | prepare-commit-msg | filepath、committype、sha-1 | \ |
提交工做流鉤子 | commit-msg | filepath | 是 |
提交工做流鉤子 | post-commit | \ | \ |
電子郵件工做流鉤子 | applypatch-msg | merge-filename | 是 |
電子郵件工做流鉤子 | pre-applypatch | \ | 是 |
電子郵件工做流鉤子 | post-commit | \ | 否 |
其它客戶端鉤子 | pre-rebase | \ | 是 |
其它客戶端鉤子 | post-rewrite、post-checkout 和 post-merge | commandname | \ |
其它客戶端鉤子 | pre-push | originbranhname & head | 是 |
服務器端鉤子主要有三種:bash
鉤子名稱 | 接收參數 | 能否終止操做 |
---|---|---|
pre-receive | 推送的引用 | 是 |
update | 引用的名字(分支),推送前的引用指向的內容的 SHA-1 值,以及用戶準備推送的內容的 SHA-1 值 | 是 |
post-receive | 同pre-receive | 否 |
Git 的鉤子無論客戶端鉤子仍是服務端鉤子,都是放在當前項目的 .git/hooks
目錄下。不一樣的是,客戶端鉤子是放置在你的本地項目的目錄下,而服務器端鉤子是放在對應的服務器上的目錄。服務器
咱們知道 Git 至關於本地的文件數據庫,而 .git
目錄存放了項目文件的快照以及其餘一系列 git 信息,且 .git
目錄是不會被提交到服務器上的,因此放置在 .git/hooks
目錄中的客戶端腳本也不會被提交。因此若是想讓項目中的其餘人使用你的鉤子,就須要一種策略來偷偷的安裝這個鉤子或者在服務端放置實現這個鉤子的功能。app
鉤子都被存儲在 Git 目錄下的 hooks 子目錄中。 也即絕大部分項目中的 .git/hooks 。 當你用 git init 初始化一個新版本庫時,Git 默認會在這個目錄中放置一些示例腳本。這些腳本除了自己能夠被調用外,它們還透露了被觸發時所傳入的參數。 全部的示例都是 shell 腳本,其中一些還混雜了 Perl 代碼,不過,任何正確命名的可執行腳本均可以正常使用 —— 你能夠用 Ruby 或 Python,或其它語言編寫它們。 這些示例的名字都是以 .sample 結尾,若是你想啓用它們,得先移除這個後綴。
把一個正確命名且可執行的文件放入 Git 目錄下的 hooks 子目錄中,便可激活該鉤子腳本。
以後我會用 Node.js 來寫一個拒絕提交沒有被解決的衝突的文件
的鉤子。
須要的知識儲備:
會寫 Javascript
瞭解一點環境變量的知識
瞭解 Nodejs require 路徑規則
寫這個鉤子的初衷是由於在多人合做項目中,老是不免會遇到文件衝突的狀況,而有些同事沒有找到所有的衝突文件並一一解決,這個鉤子就會在 commit 的時候檢查是否有衝突,若是有衝突,就會把全部衝突找到,並提示出錯文件後,拒絕 commit。
直接上源碼:
#!/usr/bin/env node // 在 commit 以前檢查是否有衝突,若是有衝突就 process.exit(1) const execSync = require('child_process').execSync // git 對全部衝突的地方都會生成下面這種格式的信息,因此寫個檢測衝突文件的正則 const isConflictRegular = "^<<<<<<<\\s|^=======$|^>>>>>>>\\s" let results try { // git grep 命令會執行 perl 的正則匹配全部知足衝突條件的文件 results = execSync(`git grep -n -P "${isConflictRegular}"`, {encoding: 'utf-8'}) } catch (e) { console.log('沒有發現衝突,等待 commit') process.exit(0) } if(results) { console.error('發現衝突,請解決後再提交,衝突文件:') console.error(results.trim()) process.exit(1) } process.exit(0)
把這個文件拷貝到 .git/hooks/pre-commit
下,並執行 chmod 777 pre-commit
就能夠在每次 commit 的狀況下檢查以前文件是否有衝突。
試想一下,咱們在寫鉤子的時候,並不會一次就把代碼寫對,因此須要常常把這個文件拷貝到 .git/hooks
目錄下;有沒有更好的作法? 有的。只須要在.git/hooks
下面建立一個 shell 腳本,來調用這個 js 文件便可。
#!/usr/bin/env node const execSync = require('child_process').execSync execSync("./pre-commit.js" )
這中 shebang
寫法在使用 git 的命令來運行的時候是沒有問題的,可是在使用 Source Tree
的 Git-GUI,會報 node 命令不存在, 這是新版本的 osx 的安全策略形成的(能夠運行 which node 命令看看和上面的 shebang 有什麼區別),對於這種狀況使用下面的腳本能夠完美解決。
#!/usr/bin/env bash # 支持 sourcetree export PATH=/usr/local/bin:$PATH node "./pre-commit.js"
NOTE:
注意 node './pre-commit.js'
這個路徑,是指若是在當前項目的根目錄下運行 git commit
,因此 pre-commit.js
是相對於當前根目錄的路徑。想優化的話能夠經過 Git 的一些默認環境變量來配置。
到這裏就基本結束了,可是咱們再回憶下以前說過的內容『客戶端鉤子是不會被其餘項目成員 clone 下來的』,因此須要一種策略來保證項目中每一個成員都安裝了這個鉤子。因爲咱們的前端項目是須要每一個成員都經過 npm start 命令開啓服務的,因此能夠在 npm start 中作些手腳。
const fs = require('fs'); // 判斷是否已經存在 pre-commit,不存在就讀取 pre-commit.sh 並寫入 if (!fs.existsSync('.git/hooks/pre-commit')) { if(!fs.existsSync('.git/hooks/')) { fs.mkdirSync('.git/hooks/'); } let preCommitFile = fs.readFileSync('./pre-commit.sh'); fs.writeFileSync('.git/hooks/pre-commit', preCommitFile, { encoding: 'utf8', mode: 0o777 }); }
凡是能被 JS 重寫的項目,最終必定會被 JS 重寫。