#!/bin/bash
# Copyright (c) 2017-2026 Jean-Louis Leroy
# Distributed under the Boost Software License, Version 1.0.
# See accompanying file LICENSE_1_0.txt
# or copy at http://www.boost.org/LICENSE_1_0.txt)

# Reject a commit that stages an oversized file. Bypass with `git commit
# --no-verify` when a large file really is project content. The "File size"
# CI job runs the same check (via dev/check-blob-size) server-side, over
# every blob a pull request's commits introduce, so a large file can't reach
# develop just because a contributor skipped installing this hook.
#
# Not installed automatically - run dev/install-git-hooks once per clone (or
# per worktree that doesn't share this one's .git/hooks; linked worktrees do).
#
# Sizes come from the index (`git cat-file -s :path`, inside
# dev/check-blob-size), not the working tree, so this is right for
# `git add -p`, for `git commit <paths>` (git hands the hook a temporary
# index), and on a repo with no commits yet. --diff-filter=ACMR skips
# deletions, which have no index entry to size.
#
# bash, not sh: `read -d` is a bashism, and dash is /bin/sh here. The redirect
# from a process substitution keeps the loop out of a subshell, so `status`
# survives it and every offender gets reported, not just the first.

# Prefer this worktree's own copy of the checker: git runs hooks with the
# working tree's top level as the current directory, so a plain relative path
# reaches it even when .git/hooks/pre-commit is a symlink shared from another
# worktree (dev/install-git-hooks points it at the worktree it was run from).
#
# A worktree on a branch older than the script has no copy, though, and an
# unresolved command exits 127 - which would reject every commit there with a
# message about an oversized file that does not exist. So fall back to the copy
# beside the hook itself, reached through the symlink git invoked us by. Plain
# `readlink`, not `readlink -f`: the installer writes an absolute target, and
# -f is GNU-only. If even that is gone the check cannot run, and refusing every
# commit is the worse failure - say so and let the commit through.
checker=dev/check-blob-size

if [ ! -x "$checker" ]; then
    hook=$0
    [ -L "$hook" ] && hook=$(readlink "$hook")
    case $hook in
        /*) ;;
        *) hook=$(dirname "$0")/$hook ;;
    esac
    beside_hook=$(dirname "$hook")/../check-blob-size

    if [ -x "$beside_hook" ]; then
        checker=$beside_hook
    else
        echo "pre-commit: check-blob-size not found, size check SKIPPED." >&2
        exit 0
    fi
fi

status=0

while IFS= read -r -d '' f; do
    oid=$(git rev-parse ":$f") || continue
    "$checker" "$oid" "$f" || status=1
done < <(git diff --cached --name-only --diff-filter=ACMR -z)

if [ "$status" -ne 0 ]; then
    echo "pre-commit: unstage it, or commit with --no-verify." >&2
fi

exit "$status"
