git-secrets 版本演进全解析:从 1.0.0 到 1.3.0 的扫描机制、钩子行为与兼容性修复

原创2026-10-13 13:25:591,847 阅读
文章标签:应用安全开发工具

git-secrets 版本演进全解析:从 1.0.0 到 1.3.0 的扫描机制、钩子行为与兼容性修复

本文以仓库根目录下的 CHANGELOG.md 为脉络,逐版本解读 git-secrets 自 2015 年 12 月发布 1.0.0 以来,直至 1.3.0(2019-02-10)的每一次关键变更。git-secrets 是一个用于防止把密码、API 密钥等敏感信息提交进 git 仓库的 Bash 脚本工具;读懂它的版本历史,等于理解了它"如何扫描、在什么时机扫描、如何过滤误报"的完整设计。读完本文,你将掌握 --scan / --scan-history 的底层调用链、pre-commit 钩子为何必须扫描 staged 文件、.gitallowed 白名单机制、secret provider 的空输出处理,以及围绕 IFS、git exec-path、Bash 4 等环境兼容性问题的具体修复方案,并且每一条结论都能在仓库源码与 Bats 测试中找到对应依据。

总览:CHANGELOG 与代码的对应关系

CHANGELOG.md 记录了 6 个正式版本,仓库的主实现集中在根目录的单文件脚本 git-secrets(共 360 行 Bash),测试则位于 test 目录下的四组 Bats 用例(git-secrets.bats、pre-commit.bats、commit-msg.bats、prepare-commit-msg.bats)。下表先给出全局映射,后文逐版本展开:

版本 日期 核心主题 对应源码位置
1.0.0 2015-12-10 首次发布 git-secrets
1.0.1 2016-01-11 含空格文件名、超大仓库扫描 regular_grep、git_grep
1.1.0 2016-04-06 钩子扫描 staged 文件、--scan-history、.gitallowed、--cached/--no-index/--untracked pre_commit_hook、scan_history、load_allowed
1.2.0 2016-05-23 .gitallowed 忽略空行与注释、含空格文件名、数字与字符串比较修复 load_allowed
1.2.1 2016-06-27 IFS 导致的 provider "command not found" 修复 load_patterns
1.3.0 2019-02-10 空 provider 输出过滤、git exec-path 支持空格、含换行/回车模式、纯 \n 模式忽略、Bash 4 修复、IAM 扫描收窄 load_patterns、register_aws

1.0.0:初始发布确立的三大扫描管道

1.0.0 是 git-secrets 的首次发布(2015-12-10),其设计核心在 README.rst 的 Synopsis 与 Description 中给出了权威定义:

git secrets --scan [-r|--recursive] [--cached] [--no-index] [--untracked] [<files>...]
git secrets --scan-history
git secrets --install [-f|--force] [<target-directory>]
git secrets --list [--global]
git secrets --add [-a|--allowed] [-l|--literal] [--global] <pattern>
git secrets --add-provider [--global] <command> [arguments...]
git secrets --register-aws [--global]
git secrets --aws-provider [<credentials-file>]

它通过安装三个 git 钩子来拦截敏感信息入库(对应 install_all_hooks 与 install_hook):

  1. pre-commit:检查本次提交变更的文件是否命中禁止模式;
  2. commit-msg:检查提交消息是否命中禁止模式(commit_msg_hook);
  3. prepare-commit-msg:在非快进(non-fast-forward)合并时检查待合入的历史中是否存在违规内容(prepare_commit_msg_hook)。

扫描使用的正则基于 egrep(扩展正则),主脚本通过 git grep -nwHEI 或 grep -nwHEI 执行(见 git_grep 与 regular_grep),其中 -w 表示按词边界匹配、-E 为扩展正则、-I 忽略二进制文件。README.rst 特别提醒:macOS 与 Linux(BSD vs GNU)上的 egrep 版本不同,正则行为可能有差异,这是后续兼容性修复的伏笔。

从 1.0.0 开始,--install 就把三个钩子写成了极简的转发脚本——每个钩子文件的内容只有一行:

#!/usr/bin/env bash
git secrets --<hook> -- "$@"

这也是为什么 git-secrets 必须放进 PATH(README.rst 的 Installing git-secrets 一节明确要求),钩子才能被 git 正确调用。

1.0.1:文件名含空格与十万级文件仓库的扫描修正

修复一:含空格文件名的正则扫描

1.0.1(2016-01-11)修复了"仓库中包含空格的文件名在 git secrets --scan(未提供文件名、经由 git grep)时无法正确处理"的问题。从源码看,git_grep 把用户传入的文件列表通过 "${files[@]}" 以数组方式传给 git grep,并在参数列表前使用 -- 分隔(git_grep):

git_grep() {
  local options="$1"; shift
  local files=("${@}") combined_patterns=$(load_combined_patterns)
  [ -z "${combined_patterns}" ] && return 1
  GREP_OPTIONS= LC_ALL=C git grep -nwHEI ${options} "${combined_patterns}" -- "${files[@]}"
}

-- 之后的内容会被 git 视为路径而非选项,从而正确容纳带空格的路径。与之对应,pre-commit.bats 中专门有 "Rejects commits with prohibited patterns in changeset with filename that contain spaces" 用例,构造了 da ta.txt 这样的文件名并断言提交被拒绝且输出 da ta.txt:1:@todo more stuff。

修复二:数十万文件规模仓库的扫描

同版本还修复了"拥有数十万个文件的仓库在 git secrets --scan(未提供文件名)时"的可用性问题。关键在 scan() 的分流逻辑(scan):

scan() {
  local files=("${@}") options=""
  [ "${SCAN_CACHED}" == 1 ] && options+="--cached"
  [ "${SCAN_UNTRACKED}" == 1 ] && options+=" --untracked"
  [ "${SCAN_NO_INDEX}" == 1 ] && options+=" --no-index"
  # Scan using git-grep if there are no files or if git options are applied.
  if [ ${#files[@]} -eq 0 ] || [ ! -z "${options}" ]; then
    output=$(git_grep $options "${files[@]}")
  else
    output=$(regular_grep "${files[@]}")
  fi
  process_output $? "${output}"
}

当没有显式指定文件时,扫描走 git grep 而非 grep,由 git 自行枚举 git ls-files 管理的文件清单(README.rst 的 --scan 说明:"If no files are provided, all files returned by git ls-files are scanned")。这意味着扫描规模从"逐个文件传给 grep"变为"git 索引内部的批量检索",在超大仓库中开销更低、也不受命令行参数长度限制。

1.1.0:功能分水岭——staged 扫描、全历史扫描与 .gitallowed

1.1.0(2016-04-06)是功能增长最集中的版本,CHANGELOG 记录了四项变更。

变更一:pre-commit 钩子从工作区改为扫描 staged 文件

CHANGELOG 原文指出:旧版 pre-commit 钩子只扫描工作目录(working directory)而非已暂存(staged)文件,一旦工作区与暂存区发生漂移,就会漏检或误报。1.1.0 将其改为扫描 staged 文件。

源码佐证在 pre_commit_hook:

pre_commit_hook() {
  SCAN_CACHED=1
  local files=() file found_match=0 rev="4b825dc642cb6eb9a060e54bf8d69288fbee4904"
  # Diff against HEAD if this is not the first commit in the repo.
  git rev-parse --verify HEAD >/dev/null 2>&1 && rev="HEAD"
  # Filter out deleted files using --diff-filter
  while IFS= read -r file; do
    [ -n "$file" ] && files+=("$file")
  done <<< "$(git diff-index --diff-filter 'ACMU' --name-only --cached $rev --)"
  scan_with_fn_or_die "scan" "${files[@]}"
}

要点有三:

  • 通过 git diff-index --cached 拿到暂存区相对 HEAD 的差异,--diff-filter 'ACMU' 明确排除已删除(D)文件;
  • 首次提交时 HEAD 不存在,回退到空树对象 4b825dc642cb6eb9a060e54bf8d69288fbee4904(git 内置的 magic 空树 SHA),从而也能扫描首个提交;
  • SCAN_CACHED=1 会令 scan() 走 git grep --cached,即只检索索引(index)中的 blob。

pre-commit.bats 的 "Scans staged files" 用例精确复现了漂移场景:把违规内容 @todo more stuff 写入 data.txt 并 git add,随后又在工作区把同一文件改成无违规的内容,再提交——提交仍必须失败,因为检测的是 staged 版本。这正是 CHANGELOG 所描述的"working directory 与 staging directory 漂移"回归测试。

变更二:新增 --scan-history 全历史扫描

--scan-history 用于扫描仓库的全部修订历史。实现见 scan_history:

scan_history() {
  # git log does not support multiple patterns, so we need to combine them
  local combined_patterns=$(load_combined_patterns)
  [ -z "${combined_patterns}" ] && return 0
  # Looks for differences matching the patterns, reduces the number of revisions to scan
  local to_scan=$(git log --all -G"${combined_patterns}" --pretty=%H)
  # Scan through revisions with findings to normalize output
  output=$(GREP_OPTIONS= LC_ALL=C git grep -nwHEI "${combined_patterns}" $to_scan)
  process_output $? "${output}"
}

工程上有两个值得注意的设计:

  1. git log 只接受单个模式,所以先通过 load_combined_patterns 把多个 secrets.patterns 用 | 拼接成一个正则;
  2. 用 git log --all -G"<combined>" --pretty=%H 先找出"diff 内容发生变化且匹配模式"的提交,仅对这些提交执行 git grep,从而避免对整条历史全量暴搜——CHANGELOG 称之为"reduces the number of revisions to scan"。

git-secrets.bats 中有三组测试分别覆盖"历史含违规必须失败""仅工作区含违规但历史干净则 --scan-history 通过""违规分布在不同分支的历史中仍必须失败",验证 --all 确实覆盖了全部分支。

变更三:.gitallowed 白名单过滤误报

CHANGELOG 记载"Added the ability to filter false positives by using a .gitallowed file"。机制实现位于 load_allowed:

load_allowed() {
  git config --get-all secrets.allowed
  local gitallowed="$(git rev-parse --show-toplevel)/.gitallowed"
  if [ -e "$gitallowed" ]; then
    cat $gitallowed | awk 'NF && $1!~/^#/'
  fi
}

.gitallowed 放置在仓库根目录,其行会被并入 allowed 正则集合。过滤发生在 process_output:

process_output() {
  local status="$1" output="$2"
  local allowed=$(load_allowed)
  case "$status" in
    0)
      [ -z "${allowed}" ] && echo "${output}" >&2 && return 1
      # Determine with a negative grep if the found matches are allowed
      echo "${output}" | GREP_OPTIONS= LC_ALL=C grep -Ev "${allowed}" >&2 \
        && return 1 || return 0
      ;;
    1) return 0 ;;
    *) exit $status
  esac
}

其判定语义(README.rst 的 Ignoring false positives 一节有完整描述)是:先把"命中禁止模式的整行文本(含 文件名:行号:内容 前缀)"全部取出,再对整组输出做反向匹配——只有所有命中行都被至少一条 allowed 正则覆盖时才算通过;只要有一行漏网就判为违规。因此 allowed 模式天然可以把文件名、行号写进正则做精确定位,例如:

# 白名单整个文件
git secrets --add --allowed '/tmp/example:.*'
# 白名单特定文件的特定行
git secrets --add --allowed '/tmp/example:3:.*'

注意:匹配对象是"文件名:行号:行内容"拼接后的完整字符串,这也是为什么 README 提醒"allowed 模式不要过度宽泛",避免把本应拦截的行一并放行。

变更四:--scan 支持 --cached / --no-index / --untracked

这三个选项扩展了 --scan 的检索范围,实现集中在 scan() 的选项拼装与 git_grep 的 git grep 透传:

选项 语义(README.rst) 源码行为
--cached 搜索索引文件(index)中已注册的 blob git grep --cached
--no-index 搜索当前目录中未被 git 管理的文件 git grep --no-index
--untracked 在跟踪文件之外,额外扫描未跟踪文件 git grep --untracked

同时,CHANGELOG 未提及但 README 明确规定了约束:-r|--recursive 与上述三个选项互斥。该约束在 git-secrets 的入口校验中强制执行,并在 git-secrets.bats 中有专门的互斥用例(--scan -r --cached 等组合必须退出码 1)。

1.2.0:.gitallowed 解析修正与跨平台数值比较

1.2.0(2016-05-23)包含三项修复。

修复一:.gitallowed 忽略空行与 # 注释

CHANGELOG 原文:"Now ignoring empty lines and comments (e.g., #) in the .gitallowed file." 源码即上节 load_allowed 中的 awk 'NF && $1!~/^#/':NF 过滤空行(NF 为 0 的行不输出),$1!~/^#/ 过滤以 # 开头的行。

对应的测试用例非常细致:

  • git-secrets.bats "Empty lines must be ignored in .gitallowed files":空行不会让扫描意外通过;
  • git-secrets.bats "Comment lines must be ignored in .gitallowed files":文件中写入 #hash 不会放行 #hash 模式,而删除 # 写成 hash 才会放行——精确验证了注释语义。

修复二:含空格文件名的扫描(再次)

与 1.0.1 侧重 git grep 路径不同,1.2.0 修复的是"文件名含空格时 pre-commit 钩子扫描不正确"的问题。相关实现位于 regular_grep:

regular_grep() {
  local files=("${@}") patterns=$(load_patterns) action='skip'
  [ -z "${patterns}" ] && return 1
  [ ${RECURSIVE} -eq 1 ] && action="recurse"
  GREP_OPTIONS= LC_ALL=C grep -d "${action}" -nwHEI "${patterns}" "${files[@]}"
}

通过 Bash 数组 "${files[@]}" 把每个文件名作为独立参数传给 grep,从根源上避免了对含空格路径的字符串拆分。

修复三:数字与字符串比较导致的部分平台失败

CHANGELOG 原文:"Fixed an issue where numbers were being compared to strings causing failures on some platforms." 在 Bash 中,<a href="https://link.gitcode.com/i/cc0a647b5ffbe4b602dc3e2029ce591f" target="_blank">[ 1 == 1 ]] 与 [[ "1" == "1" ]] 语义不同,[[ 数字 == 字符串 ]] 在部分 shell/平台下会报错。主脚本中多处对开关量(如 FORCE、RECURSIVE、SCAN_CACHED)使用 [ ... -eq 1 ] 数值比较,例如 [install_hook 的 <a href="https://link.gitcode.com/i/3e1eea25ddfcb730d72308bf4daf54a7" target="_blank"> "${FORCE}" -ne 1 ]、[regular_grep 的 [ ${RECURSIVE} -eq 1 ]。1.2.0 统一了这类比较的写法,确保在数值与字符串处理差异较大的平台上行为一致。

1.2.1:IFS 污染导致的 provider "command not found"

1.2.1(2016-06-27)是一个小而关键的 Bug 修复,CHANGELOG 引用 PR #30 说明:secret provider 命令在执行时报 "command not found",根因是"previously set IFS variable"。

复现思路:Bash 中 IFS(Internal Field Separator)决定分词行为。若 IFS 被改成不含空格的取值(例如只含换行),那么形如 git secrets --aws-provider 的命令字符串在被展开执行时,git secrets 与 --aws-provider 的拆分就可能错乱,甚至把整条命令当成一个"命令"去查找,从而报 command not found。

修复落在 load_patterns 中执行 provider 的那一行:

load_patterns() {
  git config --get-all secrets.patterns
  # Execute each provider and use their output to build up patterns
  git config --get-all secrets.providers | while read -r cmd; do
    # Only split words on '\n\t ' and strip "\r" from the output to account
    # for carriage returns being added on Windows systems. Note that this
    # trimming is done before the test to ensure that the string is not empty.
    local result="$(export IFS=$'\n\t '; $cmd | tr -d $'\r')"
    # Do not add empty lines from providers as that would match everything.
    if [ -n "${result}" ]; then
      echo "${result}"
    fi
  done
}

export IFS=$'\n\t ' 在执行 provider 前显式把 IFS 重置为"换行、制表、空格"三字符集合,local result="$(export IFS=...; $cmd ...)" 的子 shell 作用域又保证了重置只在命令替换内部生效,不会污染外层脚本的 IFS。修复后的 IFS 语义同时为 1.3.0 的"provider 命令本身带参数"(如 cat /path/to/secret/file/patterns)奠定了正确分词基础。

1.3.0:空输出过滤、Windows 路径、换行模式与更精准的 IAM 扫描

1.3.0(2019-02-10)是 CHANGELOG 中最新的版本,修复项也最多。

修复一:空 provider 输出不再被纳入模式

CHANGELOG 原文:"Empty provider output is now excluded"(对应上游 issue #34)。原因在 load_patterns 的注释中写得很直白:空行会被 load_combined_patterns 拼进 | 连接的正则,而空模式会匹配一切内容,导致整个仓库被无条件判为违规。修复逻辑即:

if [ -n "${result}" ]; then
  echo "${result}"
fi

provider 返回空串时直接跳过,不向模式集合输出任何行。git-secrets.bats 的 "Strips providers that return nothing" 用例注册了 echo(无输出)与 echo 123(有输出)两个 provider,断言只有 echo 123 被保留且扫描正常退出。

修复二:git exec-path 支持空格

CHANGELOG 原文:"Spaces are now supported in git exec path, making more Windows paths execute properly."

主脚本开头有这样一行(git-secrets):

. "$(git --exec-path)"/git-sh-setup

git 通过 git --exec-path 定位内置脚本目录(如 git-sh-setup),再以 . 命令 source 它。此前若 exec-path 直接拼接路径而未加引号,Windows 上形如 C:\Program Files\Git\mingw64\libexec\git-core 这类含空格的路径会因分词被拆开而 source 失败。现在用 "$(git --exec-path)"/git-sh-setup 双引号包裹命令替换结果,整个路径作为一个整体参与拼接,含空格的安装目录即可正常加载。

修复三:带换行与回车的模式正确加载,纯 \n 模式被忽略

CHANGELOG 两条关联变更:

  • "Patterns with newlines and carriage returns are now loaded properly."
  • "Patterns that contain only "\n" are now ignored."

前者与 Windows 有关:CRLF 环境下 provider 输出会带 \r 回车符,load_patterns 里 $cmd | tr -d $'\r' 统一剥掉回车,避免模式末尾粘上 \r 导致匹配失效;同时 provider 输出按行读取后进入模式集合并以 | 拼接,换行被正确处理为"多个模式"而非"一个含换行的畸形正则"。

后者是前者的边界条件:如果 provider 输出的只有 \n(即空行),tr 剥离后剩余为空串,配合 [ -n "${result}" ] 检查被直接丢弃——与修复一共享同一判定路径,防止空行污染 load_combined_patterns 的 | 拼接。

修复四:Various Bash 4 fixes

CHANGELOG 引用上游 issue #66 归纳了针对 Bash 4 的一系列修正。从代码现状可看到几类典型做法:

  • 数组遍历统一使用 "${files<a href="https://link.gitcode.com/i/8d0fe5203c5078fef79fb4e3473eb1f9" target="_blank">@]}" 引号展开([scan、git_grep、regular_grep),避免 Bash 4 下未加引号的数组展开行为差异导致文件名被分词;
  • 进程替换 <<< "$(git diff-index ...)" 配合 IFS= read -r file 逐行读取(pre_commit_hook),确保以换行为唯一分隔、保留行内空格;
  • provider 执行时 export IFS=$'\n\t ' 显式重置分词集合(load_patterns),兼容 Bash 4 对 IFS 继承与子 shell 导出的细微差别。

这些写法保证脚本在 Bash 4.x(macOS 默认 Bash 即长期停留在 3.2,Linux 发行版普遍为 4.x+)环境下行为一致。

修复五:Make IAM key scanning much more targeted

CHANGELOG 原文:"Make IAM key scanning much more targeted." 对应 register_aws 中注册的 AWS 模式集合。1.3.0 之前仅注册通用的 Access Key ID 前缀模式,如今 --register-aws 写入的配置更为精细:

add_config 'secrets.patterns' '(A3T[A-Z0-9]|AKIA|AGPA|AIDA|AROA|AIPA|ANPA|ANVA|ASIA)[A-Z0-9]{16}'
add_config 'secrets.patterns' 'ABSK[A-Za-z0-9+/]{109,}=*' #Bedrock long-lived
add_config 'secrets.patterns' 'bedrock-api-key-YmVkcm9jay5hbWF6b25hd3MuY29t' #Bedrock short-lived
add_config 'secrets.patterns' "${opt_quote}${aws}(SECRET|secret|Secret)?_?(ACCESS|access|Access)?_?(KEY|key|Key)${opt_quote}${connect}${opt_quote}[A-Za-z0-9/\+=]{40}${opt_quote}"
add_config 'secrets.patterns' "${opt_quote}${aws}(ACCOUNT|account|Account)_?(ID|id|Id)?${opt_quote}${connect}${opt_quote}[0-9]{4}\-?[0-9]{4}\-?[0-9]{4}${opt_quote}"
add_config 'secrets.allowed' 'AKIAIOSFODNN7EXAMPLE'
add_config 'secrets.allowed' "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"

具体改进点:

  • Access Key ID 的校验位规则更严格:前缀限定为 A3T/AKIA/AGPA/AIDA/AROA/AIPA/ANPA/ANVA/ASIA,后接恰好 16 位 [A-Z0-9];
  • 新增 Amazon Bedrock API key 模式:长期 key 形如 ABSK[A-Za-z0-9+/]{109,}=*,短期 key 为固定的 bedrock-api-key-YmVkcm9jay5hbWF6b25hd3MuY29t 前缀;
  • Secret Access Key 与 Account ID 的赋值模式要求 : 或 =(允许可选的引号包裹),且分别限定 40 位 base64 字符与 4-4-4 位账户号格式,减少宽泛匹配;
  • 同时注册 AWS 官方示例 key(AKIAIOSFODNN7EXAMPLE 与 wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY)为 allowed 模式,避免文档示例误报。

以上模式在 git-secrets.bats 的 "Adds common AWS patterns" 用例中被逐一断言存在。需要说明的是,README.rst 在 --register-aws 的说明中专门加了 note:这些模式"不保证捕获所有 AWS 凭据实例",git-secrets 只是额外保险,用户仍需自行尽职检查;这也适用于本文介绍的所有内置模式。

贯穿版本的三条主线:从 CHANGELOG 看设计原则

梳理 6 个版本,可以提炼出三条贯穿始终的工程主线,它们也是使用 git-secrets 时最重要的心智模型:

1. 扫描对象从"工作区"走向"git 索引与历史" 1.0.1 让无参 --scan 走 git grep(基于 git ls-files),1.1.0 让 pre-commit 只扫 staged 文件并新增 --scan-history 与 --cached,最终形成"提交时扫暂存、合并时扫历史、手动全历史体检"的完整覆盖。这也解释了为什么 pre-commit 钩子的输出格式统一为 文件名:行号:内容——它既是人类可读的错误信息,也是 process_output 做 allowed 反向匹配时的输入格式。

2. 模式集合的动态组装必须严防"空模式" load_patterns → load_combined_patterns → git grep -E 的链路中,任何 provider 输出的空行都会变成 | 拼接出的空分支(匹配一切)。1.2.1 修复 IFS 保证 provider 命令能被正确拆分执行,1.3.0 又增加 [ -n "${result}" ] 空输出过滤与 tr -d $'\r' 回车剥离——两条修复共同守护这条链路的输入纯净性。

3. 兼容性修复集中在"引号、IFS、数组展开"三处 Bash 特性 含空格路径(1.0.1、1.2.0)、Windows 路径(1.3.0)、provider 命令找不到(1.2.1)、Bash 4 行为差异(1.3.0),本质都是 Bash 分词与展开规则在不同环境下表现不同。最终的代码形态统一为:数组参数用 "${files[@]}"、路径拼接用双引号包裹、IFS 在子 shell 中显式重置、逐行读取用 IFS= read -r。这些写法值得在同类 Bash 工具中直接复用。

验证与回归:如何用仓库自带的 Bats 测试复现修复

仓库使用 bats(Bash Automated Testing System)做回归测试,Makefile 的 test 目标直接驱动:

make test
# 等价于:LANG=C test/bats/bin/bats test/

测试覆盖与 CHANGELOG 的对应关系如下,读者可以据此逐条复现本文章结论:

CHANGELOG 条目 对应测试
1.0.1 含空格文件名 pre-commit.bats
1.1.0 扫描 staged 文件 pre-commit.bats
1.1.0 --scan-history git-secrets.bats
1.2.0 .gitallowed 忽略空行/注释 git-secrets.bats
1.1.0 .gitallowed 过滤机制 git-secrets.bats
1.3.0 空 provider 输出过滤 git-secrets.bats
1.3.0 收窄 IAM 模式 git-secrets.bats
1.1.0 互斥选项校验 git-secrets.bats
钩子安装(含 Debian 风格目录、模板目录) git-secrets.bats

四个钩子行为测试文件 pre-commit.bats、commit-msg.bats、prepare-commit-msg.bats 则分别验证三个钩子的拒绝/放行语义,其中 prepare-commit-msg.bats 的 "Rejects merges with prohibited patterns in history" 用例,正是对"--no-ff 合并必须拦截历史违规"这一 README 承诺的直接回归。

结语:版本历史即使用手册

git-secrets 的 CHANGELOG 虽短,却浓缩了它在三年多时间里处理真实问题的全部思路:扫描时机从工作区收束到暂存区、检索能力从单文件扩展到索引与全历史、模式管理从纯禁止模式扩展出 .gitallowed 与 secret provider 双层过滤、兼容性从 GNU/Linux 延伸到 macOS 与 Windows 的 Bash 差异。读懂这份 CHANGELOG,就基本掌握了 git-secrets 的能力边界与实现原理;配合 git-secrets 主脚本与 test 目录下的 Bats 用例,你既能回答"某个功能为什么这样设计",也能在遇到误报或漏报时准确定位到对应版本的修复逻辑。

登录后查看全文
git-secrets