说在前面
不知道大家有没有遇到这样一种情况,平时在写代码调试时有时候会使用到debugger
,可能大部分时间在提交代码前会记得把debugger
先删除,但可能也会存在将debugger
提交上去的情况,那我们该怎么防止出现这种情况呢?
webpack 配置修改
开发过程中,经常需要使用console.log
、console.info
、alert
等操作来输出内容,测试代码,而在生产环境之中,这些打印的东西最好是不要显示、特别是用户名、密码相关。
一个个去删除、注释显然是很麻烦的一件事,所以我们可以通过修改配置变量,实现在开发环境打印,而生产环境不打印。
修改方法如下:
在项目的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-commit
和git-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的发生。钩子是用两个参数调用的,这两个参数提供了目标远程的名称和位置,如果没有使用命名的远程,两个值将是相同的。关于要推送的内容的信息在钩子的标准输入中提供,格式如下:
SP SP SP LF
例如,如果命令
git push origin master:foreign
被运行,钩子会收到像下面这样的一行:参考/头/主67890参考/头/外12345
尽管将提供完整的对象名称。如果外部ref还不存在,<远程对象名>将是全零对象名。如果要删除一个ref,
将作为(delete)提供,
将是全零对象名称。如果本地提交不是由可以扩展的名称(例如HEAD~,或者对象名称)指定的,那么它将按照最初给出的方式提供。
如果这个钩子以非零状态退出,git push将不推送任何东西而中止。关于拒绝推送的原因的信息可以通过写入标准错误发送给用户。
- pre-rebase
这个钩子由
git-rebase
调用,可以用来防止分支被rebase
。钩子可以带一个或两个参数调用。第一个参数是序列分叉的上游。第二个参数是正在重基的分支,在重基当前分支时不设置。
- pre-receive
当git-receive-pack响应git push并更新其存储库中的引用时,会调用该钩子。就在开始更新远程存储库上的引用之前,调用预接收钩子。其退出状态决定了更新的成功或失败。
这个钩子执行一次接收操作。它不接受任何参数,但对于每个要更新的ref,它会在标准输入中接收一行格式如下:
SP SP LF
其中
是存储在ref中的旧对象名称,
是存储在ref中的新对象名称,
是ref的全名。创建新ref时,
是全零对象名称。
如果钩子以非零状态退出,则不会更新任何引用。如果钩子以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 fetch
与git push
相反的方向运行,因为git read-tree -u -m
的两树形式本质上与git switch
或git 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
说在后面
🎉 这里是 JYeontu,现在是一名前端工程师,有空会刷刷算法题,平时喜欢打羽毛球 🏸 ,平时也喜欢写些东西,既为自己记录 📋,也希望可以对大家有那么一丢丢的帮助,写的不好望多多谅解 🙇,写错的地方望指出,定会认真改进 😊,偶尔也会在自己的公众号(前端也能这么有趣)发一些比较有趣的文章,有兴趣的也可以关注下。在此谢谢大家的支持,我们下文再见 🙌。