Git Hooks实战:提交前检查修改文件中是否包含调试代码

简介: Git Hooks实战:提交前检查修改文件中是否包含调试代码

说在前面

不知道大家有没有遇到这样一种情况,平时在写代码调试时有时候会使用到debugger,可能大部分时间在提交代码前会记得把debugger先删除,但可能也会存在将debugger提交上去的情况,那我们该怎么防止出现这种情况呢?

webpack 配置修改

开发过程中,经常需要使用console.logconsole.infoalert等操作来输出内容,测试代码,而在生产环境之中,这些打印的东西最好是不要显示、特别是用户名、密码相关。

一个个去删除、注释显然是很麻烦的一件事,所以我们可以通过修改配置变量,实现在开发环境打印,而生产环境不打印。

修改方法如下:

在项目的build/webpack.prod.conf.js文件之中,找到UglifyJsPlugin配置,在compress中添加如下代码即可。

new UglifyJsPlugin({
  uglifyOptions: { 
      compress: { 
          warnings: false, 
          // 打包的时候移除console、debugger 
          drop_debugger: true, // 移除debugger 
          drop_console: true, // 移除console 
          pure_funcs: ['console.log','console.info']
      }
  },
  sourceMap: config.build.productionSourceMap,
  parallel: true
}),

git hook监测删除

除了直接在webpack中配置之外,我们还可以在git提交前进行监测,将修改文件中的相关语句清除,这里以清除修改文件中的debugger语句为例子:

git hook简单了解

Git钩子是一组脚本,这些脚本对应着Git仓库中的特定事件,每一次事件发生时,钩子会被触发。这允许你可以定制化Git的内部行为,在开发周期中的关键点上触发执行定制化的脚本。

钩子脚本文件通常放置于项目目录的.git/hooks文件夹下。Git会在初始化项目时自动在这个文件夹下放置一些样例脚本。如果你查看.git/hooks文件夹下,会找到如下的文件:

我们可以简单了解一下这些钩子的触发时机:

  • applypatch-msg

这个钩子由git-am调用。它接受一个参数,即保存提议的提交日志消息的文件的名称。以非零状态退出导致git am在应用补丁之前中止。

允许钩子就地编辑消息文件,并可用于将消息规范化为某种项目标准格式。它还可以用于在检查消息文件后拒绝提交。

默认的applypatch-msg钩子在启用时,如果启用了commit-msg钩子,则运行commit-msg钩子。

  • commit-msg

这个钩子由git-commitgit-merge调用,可以通过——no-verify选项绕过。它接受一个参数,即保存提议的提交日志消息的文件的名称。以非零状态退出将导致命令中止。

允许钩子就地编辑消息文件,并可用于将消息规范化为某种项目标准格式。它还可以用于在检查消息文件后拒绝提交。

默认的commit-msg钩子在启用时,会检测到重复的挂件签到,如果发现了,就会中止提交。

  • fsmonitor-watchman

这个钩子在配置选项核心时被调用。根据要使用的钩子的版本,Fsmonitor被设置为.git/hooks/ Fsmonitor -watchman或.git/hooks/ Fsmonitor -watchmanv2。

Version 1有两个参数,一个是Version(1),另一个是自1970年1月1日午夜以来经过的纳秒时间。

Version 2有两个参数,一个是Version(2),另一个是token(用于标识token之后的更改)。对于守望者来说,这是一个时钟id。这个版本必须在文件列表之前向stdout输出新的令牌,后跟一个NUL。

钩子应该向stdout输出工作目录中自请求时间以来可能发生更改的所有文件的列表。逻辑应该是包容性的,这样它就不会错过任何潜在的更改。这些路径应该是相对于工作目录的根目录的,并由单个NUL分隔。

包含实际上没有更改的文件是可以的。应该包括所有更改,包括新创建和删除的文件。当重命名文件时,应该同时包含旧的和新的名称。

Git将根据给定的路径名限制检查更改的文件,以及检查未跟踪文件的目录。

告诉git“所有文件都已更改”的一种优化方法是返回文件名/。

退出状态决定git是否会使用来自钩子的数据来限制它的搜索。如果出现错误,它将退回到验证所有文件和文件夹。

  • post-update

git-receive-pack响应git push并更新其存储库中的引用时,会调用该钩子。当所有引用都被更新后,它会在远程存储库上执行一次。

它接受可变数量的参数,每个参数都是实际更新的ref的名称。

这个钩子主要用于通知,不会影响git receive-pack的结果。

post-update钩子可以告诉我们被推送的头是什么,但它不知道它们的原始值和更新值是什么,所以这是一个糟糕的地方。post-receive钩子获取ref的原始值和更新后的值。如果你需要的话,你可以考虑一下。

当启用时,默认的post-update钩子会运行git update-server-info来保持哑传输(例如HTTP)使用的信息是最新的。如果你要发布一个可以通过HTTP访问的Git存储库,你应该启用这个钩子。

标准输出和标准错误输出都被转发到另一端的git send-pack,因此您可以简单地为用户回显消息。

  • pre-commit

这个钩子由git-commit调用,可以通过——no-verify选项绕过。它不接受任何参数,在获得建议的提交日志消息并进行提交之前调用。以非零状态退出该脚本会导致git commit命令在创建提交之前终止。

默认的预提交钩子在启用时,会捕获带有尾随空格的行,并在找到这样的行时中止提交。

所有的git提交钩子都是用环境变量GIT_EDITOR=:来调用的,如果这个命令不会调出一个编辑器来修改提交消息。

默认的预提交钩子(当启用时)和钩子。allownonascii配置选项取消设置或设置为false -防止使用非ascii文件名。

  • pre-merge-commit

这个钩子由git-merge调用,可以用——no-verify选项绕过。它不接受任何参数,并且在成功执行合并之后,在获得建议的提交日志消息以进行提交之前调用。以非零状态退出该脚本会导致git merge命令在创建提交之前终止。

默认的预合并提交钩子在启用时,如果启用了预提交钩子,则运行预提交钩子。

如果该命令不会调出编辑器来修改提交消息,则使用环境变量GIT_EDITOR=:调用该钩子。

如果合并不能自动执行,则需要解决冲突并单独提交结果(参见git-merge[1])。此时,此钩子将不会被执行,但如果启用了预提交钩子,则会执行。

  • prepare-commit-msg

这个钩子被git-commit调用,在准备默认日志消息之后,在编辑器启动之前。

它需要一到三个参数。第一个是包含提交日志消息的文件的名称。第二个是提交消息的来源,可以是:message(如果给出了-m或-F选项);模板(如果给出了-t选项或配置选项commit)。模板设置);merge(如果提交是merge或者.git/MERGE_MSG文件存在);(如果.git/SQUASH_MSG文件存在);或commit,后跟提交对象名称(如果给出了-c、-c或——amend选项)。

如果退出状态非零,git commit将中止。

钩子的目的是就地编辑消息文件,它不会被——no-verify选项抑制。非零退出意味着钩子失败并终止提交。它不应该被用作预提交钩子的替代品。

Git附带的样例prepare-commit-msg钩子删除了提交模板注释部分中的帮助消息。

  • pre-push

这个钩子由git-push调用,可以用来阻止push的发生。钩子是用两个参数调用的,这两个参数提供了目标远程的名称和位置,如果没有使用命名的远程,两个值将是相同的。

关于要推送的内容的信息在钩子的标准输入中提供,格式如下:

<local ref> SP <local object name> SP <remote ref> SP <remote object name> LF

例如,如果命令git push origin master:foreign被运行,钩子会收到像下面这样的一行:

参考/头/主67890参考/头/外12345

尽管将提供完整的对象名称。如果外部ref还不存在,<远程对象名>将是全零对象名。如果要删除一个ref, <local ref>将作为(delete)提供,<local object name>将是全零对象名称。如果本地提交不是由可以扩展的名称(例如HEAD~,或者对象名称)指定的,那么它将按照最初给出的方式提供。

如果这个钩子以非零状态退出,git push将不推送任何东西而中止。关于拒绝推送的原因的信息可以通过写入标准错误发送给用户。

  • pre-rebase

这个钩子由git-rebase调用,可以用来防止分支被rebase。钩子可以带一个或两个参数调用。第一个参数是序列分叉的上游。第二个参数是正在重基的分支,在重基当前分支时不设置。

  • pre-receive

当git-receive-pack响应git push并更新其存储库中的引用时,会调用该钩子。就在开始更新远程存储库上的引用之前,调用预接收钩子。其退出状态决定了更新的成功或失败。

这个钩子执行一次接收操作。它不接受任何参数,但对于每个要更新的ref,它会在标准输入中接收一行格式如下:

SP <new-value> SP <ref-name> LF

其中<old-value>是存储在ref中的旧对象名称,<new-value>是存储在ref中的新对象名称,<ref-name>是ref的全名。创建新ref时,<old-value>是全零对象名称。

如果钩子以非零状态退出,则不会更新任何引用。如果钩子以0退出,更新钩子仍然可以阻止更新单个引用。

标准输出和标准错误输出都被转发到另一端的git send-pack,因此您可以简单地为用户回显消息。

git push命令行给出的push选项数——push-option=…可以从环境变量GIT_PUSH_OPTION_COUNT中读取,选项本身在GIT_PUSH_OPTION_0, GIT_PUSH_OPTION_1,…中找到,如果协商不使用推送选项阶段,则不会设置环境变量。如果客户端选择使用推送选项,但不传输任何选项,则计数变量将被设置为零,GIT_PUSH_OPTION_COUNT=0。

有关一些注意事项,请参阅gift -receive-pack中的“隔离环境”一节。

  • push-to-checkout

git-receive-pack响应git push并更新其存储库中的引用时,以及当push尝试更新当前签出的分支并且receive.denyCurrentBranch配置变量设置为updateInstead时,该钩子被git-receive- receive-pack调用。默认情况下,如果工作树和远程存储库的索引与当前签出的提交有任何差异,则拒绝这样的推送;当工作树和索引都匹配当前提交时,它们将被更新以匹配新推送的分支尖端。此钩子将用于覆盖默认行为。

钩子接收当前分支的尖端将被更新的提交。它可以以非零状态退出以拒绝推送(当它这样做时,它不能修改索引或工作树)。或者,它可以对工作树和索引进行任何必要的更改,使它们在当前分支的尖端更新到新提交时达到所需的状态,并以零状态退出。

例如,钩子可以简单地运行git read-tree -u -m HEAD "$1",以模拟git fetchgit push相反的方向运行,因为git read-tree -u -m的两树形式本质上与git switchgit checkout相同,它们在切换分支的同时保持工作树中的本地更改,而不会干扰分支之间的差异。

  • update

git-receive-pack响应git push并更新其存储库中的引用时,会调用该钩子。就在更新远程存储库上的ref之前,调用更新钩子。它的退出状态决定了refupdate的成功或失败。

对于每个要更新的ref,钩子执行一次,并接受三个参数:

被更新的ref的名称,

旧的对象名称存储在ref,

以及要存储在ref中的新对象名称。

update钩子的零退出允许更新ref。以非零状态退出会阻止git receive-pack更新该ref。

这个钩子可以用来防止某些refs上的强制更新,通过确保对象名称是一个提交对象,是由旧对象名称命名的提交对象的后代。也就是说,执行“仅快进”策略。

它也可以用来记录新旧状态。然而,它并不知道分支的全部集合,因此在天真地使用时,它最终会为每个ref发送一封电子邮件。post-receive钩子更适合于此。

在限制用户只能通过网络访问git命令的环境中,这个钩子可以用来实现访问控制,而不依赖于文件系统所有权和组成员关系。请参阅git-shell[1]了解如何使用登录shell来限制用户只能访问git命令。

标准输出和标准错误输出都被转发到另一端的git send-pack,因此您可以简单地为用户回显消息。

默认的更新钩子,当启用时——并且带有钩子。允许unannotated配置选项unset或设置为false -防止未注释的标签被推送。

完整钩子说明,请参考官网链接

脚本逻辑

1、声明脚本运行环境

这里我们使用node进行编写,所以脚本运行环境应该声明为node

#!/usr/bin/env node
2、获取当前暂存更改的文件信息

我们可以使用git status来获取当前暂存更改的文件目录,但部分同学可能会出现获取到的文件名中文只显示字符串的情况:

  • 原因

在默认设置下,中文文件名在工作区状态输出,中文名不能正确显示,而是显示为八进制的字符编码。

  • 解决办法

将git 配置文件 core.quotepath项设置为false。

quotepath表示引用路径

加上–global表示全局配置

git bash 终端输入命令:

git config --global core.quotepath false

我们可以看到修改过的文件路径。

3、提取 git status 信息中的变动文件路径

上一步我们知道了可以使用git status命令来获取暂存更改的文件信息,但获取到的信息并不是直接得到变动的文件路径,我们需要从获取到的信息中的变动文件路径。

let commitFile = child_process.execSync(command).toString();
commitFile = commitFile.split("\n");
console.log("commitFile: ", commitFile);

这里前面的五行其实是固定的,我们只需要从第六行开始获取即可,文件路径与变更类型描述是通过:隔开,所以我们分割一下:,再将一些特殊字符去掉即可。

const child_process = require("child_process");
const trimReg = /(\ +)|([ ])|([\r\n]|(["]))/g;
const fileList = [];
for (let i = 5; i < commitFile.length; i++) {
  const name = commitFile[i].split(":")[1];
  if (!name) break;
  fileList.push(decodeURI(name.replace(trimReg, "")));
}
console.log("fileList: ", fileList);

4、判断文件中是否包含 debugger

通过readFileSync获取文件内容,判断文件内容是否包含debugger,包括的话将文件路径记录并将文件中的debugger全部删除,然后再将修改后的文件重新git add -文件路径提交即可。

const child_process = require("child_process");
const fs = require("fs");
const editFiles = [];
fileList.forEach((file) => {
  const txt = fs.readFileSync(file, "utf8");
  if (txt.includes("debugger")) {
    editFiles.push(file);
    fs.writeFileSync(file, txt.replace(/(debugger);?(\s*\n)*/g, ""));
    const commandAdd = "git add " + file;
    child_process.execSync(commandAdd).toString();
  }
});
console.log("editFiles: ", editFiles);
console.log("已清除提交文件中的 debugger");

完整代码

#!/usr/bin/env node
const child_process = require("child_process");
const fs = require("fs");
const command = "git status";
const trimReg = /(\ +)|([ ])|([\r\n]|(["]))/g;
let commitFile = child_process.execSync(command).toString();
commitFile = commitFile.split("\n");
const fileList = [];
for (let i = 5; i < commitFile.length; i++) {
  const name = commitFile[i].split(":")[1];
  if (!name) break;
  fileList.push(decodeURI(name.replace(trimReg, "")));
}
const editFiles = [];
fileList.forEach((file) => {
  const txt = fs.readFileSync(file, "utf8");
  if (txt.includes("debugger")) {
    editFiles.push(file);
    fs.writeFileSync(file, txt.replace(/(debugger);?(\s*\n)*/g, ""));
    const commandAdd = "git add " + file;
    child_process.execSync(commandAdd).toString();
  }
});
console.log("editFiles: ", editFiles);
console.log("已清除提交文件中的 debugger");
process.exit(0);

参考材料

https://zhuanlan.zhihu.com/p/521707440

https://git-scm.com/docs/githooks

源码地址

gitee

https://gitee.com/zheng_yongtao/jyeontu-templates.git

公众号

关注公众号『前端也能这么有趣』,获取更多新鲜内容。

说在后面

🎉 这里是 JYeontu,现在是一名前端工程师,有空会刷刷算法题,平时喜欢打羽毛球 🏸 ,平时也喜欢写些东西,既为自己记录 📋,也希望可以对大家有那么一丢丢的帮助,写的不好望多多谅解 🙇,写错的地方望指出,定会认真改进 😊,偶尔也会在自己的公众号『前端也能这么有趣』发一些比较有趣的文章,有兴趣的也可以关注下。在此谢谢大家的支持,我们下文再见 🙌。

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
目录
相关文章
|
1月前
|
开发工具 git C++
【git 实用指南】git下载、拉取 代码
【git 实用指南】git下载、拉取 代码
183 2
|
1月前
|
开发工具 git
【git 实用指南】git 上传代码
【git 实用指南】git 上传代码
34 2
|
1月前
|
开发工具 git
Git -- 代码上传错误 error: failed to push some refs to ‘git@gitee.com:JMFive/uni-shop2.git‘
Git -- 代码上传错误 error: failed to push some refs to ‘git@gitee.com:JMFive/uni-shop2.git‘
|
8天前
|
开发工具 git
git 拉取代码仓库代码报错(合并错误 refusing to merge unrelated histories)
git 拉取代码仓库代码报错(合并错误 refusing to merge unrelated histories)
18 0
|
11天前
|
数据可视化 开发工具 git
Git代码版本管理入门
Git代码版本管理入门
|
30天前
|
开发工具 C语言 数据安全/隐私保护
git提交代码到远端仓库的方法详解
git提交代码到远端仓库的方法详解
|
1月前
|
算法 开发工具 git
【git 实用指南】git 增加 本地代码 git add 相关命令和复杂情况需求
【git 实用指南】git 增加 本地代码 git add 相关命令和复杂情况需求
93 0
|
15天前
|
缓存 数据可视化 网络安全
Git命令大全
Git命令大全
46 1
|
19天前
|
开发工具 git
Git教程:深入了解删除分支的命令
【4月更文挑战第3天】
37 0
Git教程:深入了解删除分支的命令
|
1月前
|
存储 Shell Linux
【Shell 命令集合 文件管理】Linux git命令使用教程
【Shell 命令集合 文件管理】Linux git命令使用教程
34 0