blob: 3c562462179e90b54dcceb15741381889c877d49 [file]
#!/usr/bin/env bash
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# Fails if any file committed between a base ref and a head ref is larger than
# the size limit, the same way the "Large files PR check" GitHub workflow does.
set -euo pipefail
SCRIPT_NAME="$(basename "${BASH_SOURCE[0]}")"
ROOT_DIR="$(git rev-parse --show-toplevel)"
# 1.5 MB ought to be enough for anybody.
# TODO in case we may want to consciously commit a bigger file to the repo
# without using Git LFS we may disable the check e.g. with a label
DEFAULT_MAX_FILE_SIZE_BYTES=1572864
BASE_REF=""
HEAD_REF="HEAD"
MAX_FILE_SIZE_BYTES="${DEFAULT_MAX_FILE_SIZE_BYTES}"
usage() {
cat >&2 <<USAGE
Usage: $SCRIPT_NAME [--base <ref>] [--head <ref>] [--max-bytes <n>]
Fails if any file committed between <base> and <head> is larger than <n> bytes.
--base <ref> Start of the commit range, exclusive. Defaults to the merge base of <head> and main on the
remote that points at apache/datafusion, or on origin when there is no such remote.
--head <ref> End of the commit range, inclusive. Defaults to HEAD.
--max-bytes <n> Size limit in bytes. Defaults to ${DEFAULT_MAX_FILE_SIZE_BYTES} (1.5 MB).
USAGE
exit 1
}
while [[ $# -gt 0 ]]; do
case "$1" in
--base)
[[ $# -ge 2 ]] || usage
BASE_REF="$2"
shift
;;
--head)
[[ $# -ge 2 ]] || usage
HEAD_REF="$2"
shift
;;
--max-bytes)
[[ $# -ge 2 ]] || usage
MAX_FILE_SIZE_BYTES="$2"
shift
;;
-h|--help)
usage
;;
*)
usage
;;
esac
shift
done
# A leading zero is rejected too: bash would read the value as octal.
if ! [[ "${MAX_FILE_SIZE_BYTES}" =~ ^(0|[1-9][0-9]*)$ ]]; then
echo "[${SCRIPT_NAME}] --max-bytes must be a whole number of bytes, got \"${MAX_FILE_SIZE_BYTES}\"" >&2
exit 1
fi
cd "${ROOT_DIR}"
# The remote that points at apache/datafusion, or origin when there is no such remote.
upstream_remote() {
local remote
for remote in $(git remote); do
if [[ "$(git remote get-url "${remote}")" =~ github\.com[:/]apache/datafusion(\.git)?/?$ ]]; then
echo "${remote}"
return
fi
done
echo "origin"
}
if ! git rev-parse --verify --quiet "${HEAD_REF}^{commit}" > /dev/null; then
echo "[${SCRIPT_NAME}] ${HEAD_REF} is not a commit" >&2
exit 1
fi
if [[ -z "${BASE_REF}" ]]; then
upstream_main="$(upstream_remote)/main"
if ! git rev-parse --verify --quiet "${upstream_main}^{commit}" > /dev/null; then
echo "[${SCRIPT_NAME}] ${upstream_main} is not available; fetch it or pass --base <ref> to choose the start of the range" >&2
exit 1
fi
BASE_REF="$(git merge-base "${HEAD_REF}" "${upstream_main}")"
elif ! git rev-parse --verify --quiet "${BASE_REF}^{commit}" > /dev/null; then
echo "[${SCRIPT_NAME}] ${BASE_REF} is not a commit" >&2
exit 1
fi
echo "[${SCRIPT_NAME}] Checking files committed in ${BASE_REF}..${HEAD_REF} against the ${MAX_FILE_SIZE_BYTES} byte limit"
# Listing the objects into a variable, rather than piping them into the loop, makes a failing
# git command fail the check instead of leaving the loop with nothing to read.
objects="$(
git rev-list --objects "${BASE_REF}..${HEAD_REF}" \
| git cat-file --batch-check='%(objectname) %(objecttype) %(objectsize) %(rest)'
)"
exit_code=0
# Only blobs are files. Commits and trees are listed too, and are skipped.
while read -r id type size path; do
if [[ "${type}" == "missing" ]]; then
echo "[${SCRIPT_NAME}] Object ${id} is not in this clone, so its size cannot be checked. Fetch the full history and retry." >&2
exit 1
fi
if [[ "${type}" == "blob" && "${size}" -gt "${MAX_FILE_SIZE_BYTES}" ]]; then
exit_code=1
echo "Object ${id} [${path}] has size ${size}, exceeding ${MAX_FILE_SIZE_BYTES} limit." >&2
if [[ -n "${GITHUB_ACTIONS:-}" ]]; then
echo "::error file=${path}::File ${path} has size ${size}, exceeding ${MAX_FILE_SIZE_BYTES} limit."
fi
fi
done <<< "${objects}"
exit "${exit_code}"