用 Node.js 寫前端本身的 Git-hooks

TLDR;前端

  1. 介紹 Git 鉤子的基本開發流程node

  2. 介紹如何用 Node.js 寫 Git 鉤子git

Hooks-鉤子

簡介

Git 鉤子是指在特定的 Git 動做(如:git commitgit push )下被觸發的腳本。而鉤子主要被分爲兩種:shell

  1. 客戶端鉤子數據庫

  2. 服務端鉤子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

如何用 Nodejs 寫一個鉤子

鉤子都被存儲在 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 重寫。

相關文章
相關標籤/搜索