1#!/usr/bin/env bash 2# Fail if any tracked file contains a term from .private-terms. See tools/README.md. 3# 4# tools/check-private.sh [terms-file] (default: .private-terms at the repository root) 5# tools/check-private.sh --message FILE a commit message (the commit-msg hook) 6# tools/check-private.sh --push every commit not yet on any remote: its files, names and message 7# (the pre-push hook: history is public, not just the tip) 8# 9# One term per line, matched case-insensitively as a fixed string; blank lines and lines starting 10# with # are ignored. The terms file is gitignored and never committed. Prints file:line for each 11# hit (not the matching text, so the output does not itself spread the term), exit 1 on any. 12set -euo pipefail 13cd "$(dirname "$0")/.." 14mode=tree; msgfile= 15case "${1:-}" in 16 --message) mode=message; msgfile="${2:?--message needs a file}"; shift 2 ;; 17 --push) mode=push; shift ;; 18esac 19terms="${1:-.private-terms}" 20if [ ! -f "$terms" ]; then 21 if [ "${WHISKERS_REQUIRE_PRIVATE_TERMS:-0}" = 1 ]; then 22 echo "check-private: $terms is missing and WHISKERS_REQUIRE_PRIVATE_TERMS=1" >&2 23 exit 2 24 fi 25 echo "check-private: no $terms; nothing to check against (see tools/README.md)" >&2 26 exit 0 27fi 28patterns="$(mktemp)" 29trap 'rm -f "$patterns"' EXIT 30grep -v -e '^[[:space:]]*$' -e '^[[:space:]]*#' "$terms" | sed 's/\r$//' > "$patterns" || true 31if [ ! -s "$patterns" ]; then 32 echo "check-private: $terms has no terms" >&2 33 exit 0 34fi 35if [ "$mode" = message ]; then 36 if grep -q -i -F -f "$patterns" "$msgfile"; then 37 echo "check-private: the commit message contains a private term; reword it" >&2 38 exit 1 39 fi 40 echo "check-private: message clean" 41 exit 0 42fi 43if [ "$mode" = push ]; then 44 # Commits that are on no remote yet: what a push would publish. Files, names and messages of each. 45 bad=0 46 for c in $(git rev-list HEAD --not --remotes); do 47 h="$(git grep -I -l -i -F -f "$patterns" "$c" -- . ':!.private-terms' ':!third-party/pixel-icons' ':!crates/whiskers-icons/allowlist.txt' || true)" 48 n="$(git ls-tree -r --name-only "$c" | grep -v -e '^third-party/pixel-icons/' -e '^crates/whiskers-icons/allowlist.txt$' | grep -i -F -f "$patterns" || true)" 49 if [ -n "$h$n" ] || git log -1 --format=%B "$c" | grep -q -i -F -f "$patterns"; then 50 echo "check-private: commit $(git log -1 --format=%h "$c") would publish a private term (files, names or message); rewrite it before pushing" >&2 51 bad=1 52 fi 53 done 54 [ "$bad" = 0 ] || exit 1 55 echo "check-private: the commits to push are clean" 56 exit 0 57fi 58# -I skips binary files, -n gives line numbers; -o is not used so the term is not echoed. 59# Not checked: the vendored icon pack (its file names and README are upstream's, and a short term matches 60# inside ordinary words) and the allowlist that has to name each of its icons. 61skip=(':!.private-terms' ':!third-party/pixel-icons' ':!crates/whiskers-icons/allowlist.txt') 62hits="$(git grep -I -n -i -F -f "$patterns" -- . "${skip[@]}" | cut -d: -f1,2 || true)" 63# Binary files (screenshots) are checked by name only. 64names="$(git ls-files -- . "${skip[@]}" | grep -i -F -f "$patterns" | sed 's/$/:0 (file name)/' || true)" 65all="$(printf '%s\n%s\n' "$hits" "$names" | sed '/^$/d')" 66if [ -n "$all" ]; then 67 echo "check-private: a private term appears in tracked files:" >&2 68 echo "$all" >&2 69 exit 1 70fi 71echo "check-private: clean ($(wc -l < "$patterns") term(s) checked)"