#!/bin/bash set -f +o posix && shopt -s extglob || exit function die { printf '%s\n' "$1" exit "${2-1}" } function get_common_prefix { local a b i IFS=/ a=($1) b=($2) i=0 for (( ; i < ${#a[@]}; ++i )); do [[ ${a[i]} == "${b[i]}" ]] || break done __=${a[*]:0:i} } function get_common_prefix_multi { __=${1%/*} while shift; [[ ${1+.} ]]; do get_common_prefix "$__" "${1%/*}" done } function check_commit_cleanup { local value if ! value=$(git config commit.cleanup); then echo "WARNING: Failed to get value of 'commit.cleanup'." echo elif [[ ${value} != strip ]]; then echo "WARNING: Git's config variable 'commit.cleanup' is not set to 'strip'." echo fi } function call { local x q m= for x do printf -v q %q "$x" if [[ $q == "$x" ]]; then m+=" $x" elif [[ $x == *\'* ]]; then m+=" $q" else m+=" '$x'" fi done printf '> %s\n' "${m# }" command "$@" } function all_files_deleted { local dir=$1 [[ -z $(git ls-files --cached -- "${dir}") ]] # Simple solution discovered and shared by osse } function main { local copyright_year current_year ebuilds files status test_dir= value __ check_commit_cleanup status=$(git status --porcelain --find-renames="100%") # Check copyright year in ebuilds; ignore pure renames. readarray -t files < <(printf '%s' "${status}" | \ gawk 'match($0, /^[ACMT]. (.* -> )?(.*\.ebuild)$/, a) { print a[2] }') printf -v current_year '%(%Y)T' for f in "${files[@]}"; do copyright_year=$(git show ":./$f" | gawk 'match($0, /^# Copyright ([[:digit:]]+-)?([[:digit:]]+) /, a) { print a[2]; exit }') [[ ${copyright_year} != "${current_year}" ]] && \ die "Copyright year of '$f' is invalid: ${copyright_year}" done # Immediately check for unindexed files that belong to a package starting # from where the git command was executed. readarray -t unindexed < <(printf '%s' "${status}" | \ gawk 'BEGIN { p = ENVIRON["GIT_PREFIX"] } !/^. / && !/^!!/ && match($0, /^(.. )(.*)/, a) && substr(a[2], 0, length(p)) == p && a[2] ~ /^[^/-]+-[^/-]+\/[^/-]+/ { print a[1] a[2] }') if [[ ${#unindexed[@]} -gt 0 ]]; then local IFS=$'\n' __ printf -v __ 'Unindexed modifications found: \n\n%s\n' "${unindexed[*]/#/ }" die "$__" fi # Following tests are worktree based. # # The first step is to get the common prefix. readarray -t files < <(printf '%s' "${status}" | \ gawk 'match($0, /^[ACDMRT]. (.* -> )?(.*)/, a) { print a[2] }') if [[ ${#files[@]} -gt 0 ]]; then get_common_prefix_multi "${files[@]}" if [[ -z $__ || $__ == +([!/-])-+([!/-]) || $__ == +([!/-])-+([!/-])/* ]]; then if [[ $__ ]]; then local IFS=/ set -- $__ test_dir="${*:1:2}" fi # We can't allow unindexed modifications in "${test_dir}" because the main tests will be # affected by those. # # The "not all files are currently deleted but directory doesn't exist in worktree" test # below might be overshadowed by this but we'll keep the test anyway for the sake of # consistency. # # Don't exclude directories since files within it do not appear when the directory # itself isn't indexed. readarray -t unindexed < <(git status --porcelain -- "${test_dir:-.}" | \ gawk '!/^. / && !/^!!/') if [[ ${#unindexed[@]} -gt 0 ]]; then local IFS=$'\n' __ printf -v __ 'Unindexed modifications found: \n\n%s\n' "${unindexed[*]/#/ }" die "$__" fi # Test directory not existing means whole category or package was deleted. # # It can also be another non-category/non-package directory but its parent # directory doesn't need to be tested for it. if [[ -z ${test_dir} ]] || ! all_files_deleted "${test_dir}"; then [[ -z ${test_dir} || -e ${test_dir} ]] || \ die "Not all files are currently deleted but directory doesn't exist in worktree: ${test_dir}" [[ -z ${test_dir} || -d ${test_dir} ]] || \ die "Unexpectedly not a directory: ${test_dir}" readarray -t ebuilds < <(find "${test_dir:-.}" -name '*.ebuild' -exec grep -Ple \ "KEYWORDS=.*[ \"'=][[:lower:]][[:alnum:]-]+" -- '{}' +) if [[ ${#ebuilds[@]} -gt 0 ]]; then echo "The following ebuilds have stable keywords: " printf '%s\n' "${ebuilds[@]}" return 1 fi if [[ ${GIT_PRE_COMMIT_SKIP_REPOMAN-} != @(true|1) ]] && type -P repoman >/dev/null; then echo Repoman: echo "Set GIT_PRE_COMMIT_SKIP_REPOMAN to 'true' to disable." ( [[ -z ${test_dir} ]] || cd -- "${test_dir}" && call repoman ) || return 1 echo fi if [[ ${GIT_PRE_COMMIT_SKIP_PKGCHECK_SCAN-} != @(true|1) ]] && type -P pkgcheck >/dev/null; then local pkgcheck=(pkgcheck scan --exit=error) if [[ -e helpers/profiles.mask ]]; then readarray -t profiles < helpers/profiles.mask || return 1 IFS=, eval 'pkgcheck+=("--profiles=${profiles[*]/#/-}")' fi echo "Pkgcheck scan:" echo "Set GIT_PRE_COMMIT_SKIP_PKGCHECK_SCAN to 'true' to disable." ( [[ -z ${test_dir} ]] || cd -- "${test_dir}" && call "${pkgcheck[@]}" ) || \ return 1 echo fi fi fi fi return 0 } main