Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,41 @@ jobs:
cmake --build ../build --target tests -j $(nproc)
ctest --test-dir ../build -j $(nproc) --output-on-failure

file-size:
name: File size
# Only meaningful for a pull request: github.event.pull_request is unset
# on a push, and a push's commits are only ever ones already checked here
# as part of the PR that introduced them.
if: github.event_name == 'pull_request'
runs-on: ubuntu-24.04
steps:
- name: Clone Boost.OpenMethod
uses: actions/checkout@v4
with:
fetch-depth: 0

# Every blob any commit in the PR introduces, not just the files that
# differ in the final base...head diff - a commit that adds an
# oversized file and a later commit in the same PR that shrinks or
# removes it still leaves that blob in history forever once merged.
# Same limit and message as the local dev/git-hooks/pre-commit hook
# (dev/check-blob-size), so a contributor who skipped installing it
# still can't land an oversized file.
- name: Check for oversized blobs
run: |
status=0

git rev-list "${{ github.event.pull_request.base.sha }}..${{ github.event.pull_request.head.sha }}" |
while read -r c; do
git diff-tree -r --no-commit-id --diff-filter=ACMR "$c"
done | awk '{print $4, $NF}' | sort -u > /tmp/blobs.txt

while read -r oid path; do
dev/check-blob-size "$oid" "$path" || status=1
done < /tmp/blobs.txt

exit "$status"

flat-headers:
name: Flat headers
runs-on: ubuntu-24.04
Expand Down
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,8 @@ flat/
# untracked-but-addable - `git add test` would commit tens of thousands of
# lines of objects.
bin/

# CTest output. `ctest` writes Testing/Temporary/{LastTest.log,CTestCostData.txt}
# relative to the directory it runs in, so this turns up at the root whenever
# ctest is run from there rather than from build/.
Testing/
26 changes: 26 additions & 0 deletions dev/check-blob-size
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#!/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)

# Check a single blob's size against the project's oversized-file limit.
# Shared by dev/git-hooks/pre-commit (every file staged for a commit) and the
# "File size" CI job (every blob any commit in a pull request introduces), so
# the limit and the message live in one place instead of two.
#
# Usage: dev/check-blob-size <oid> <path>
# Exit 0 if the blob is within the limit (or <oid> isn't a blob at all - not
# this script's problem), 1 with a message on stderr if it's over.

limit=$((256 * 1024)) # 256 KiB

oid="$1"
path="$2"

size=$(git cat-file -s "$oid") || exit 0
if [ "$size" -gt "$limit" ]; then
printf 'oversized file: %s is %s bytes, over the %s byte limit (blob %s).\n' \
"$path" "$size" "$limit" "$oid" >&2
exit 1
fi
41 changes: 41 additions & 0 deletions dev/git-hooks/pre-commit
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#!/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. Git runs
# hooks with the working tree's top level as the current directory, so the
# plain relative path below reaches the right worktree's own copy of the
# script even when .git/hooks/pre-commit is a symlink shared from another one
# (dev/install-git-hooks points it at the worktree it was run from).

status=0

while IFS= read -r -d '' f; do
oid=$(git rev-parse ":$f") || continue
dev/check-blob-size "$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"
23 changes: 23 additions & 0 deletions dev/install-git-hooks
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#!/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)

# Symlink dev/git-hooks/* into the hooks directory git will actually run -
# .git/hooks in a plain clone, or the common git dir for a linked worktree, so
# one run covers every worktree of this clone.
#
# Usage: dev/install-git-hooks

set -euo pipefail

root_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
hooks_src="$root_dir/dev/git-hooks"
hooks_dst="$(cd "$root_dir" && git rev-parse --git-path hooks)"

for hook in "$hooks_src"/*; do
name="$(basename "$hook")"
ln -sf "$hook" "$hooks_dst/$name"
echo "installed $hooks_dst/$name -> $hook"
done
Loading