用 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 重写。

相关文章
相关标签/搜索