#!/usr/bin/env bash # Steps of the reusable workflow .gitea/workflows/code-metrics.yml (ADR-0010). # The workflow checks out the calling repository, fetches this file into # shared-scripts/ and runs one command per step: # # code-metrics.sh validate check the inputs, before anything is counted # code-metrics.sh measure count the code, read the coverage report and # write the badge files (count_loc.py, badges.py) # code-metrics.sh publish commit the badge files to the badges branch, # and nothing when none of them changed # # Settings come from the environment, never from interpolated expressions: # WORKING_DIRECTORY, EXCLUDE, COVERAGE_REPORT, COVERAGE_ARTIFACT, # COVERAGE_WARN, COVERAGE_GOOD, BADGES_BRANCH, BADGES_OUT (the directory of the badge files; a temporary # directory under RUNNER_TEMP by default). # Every failure is printed as ::error::[category] message. TOOLS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" OUT_DIR="${BADGES_OUT:-${RUNNER_TEMP:-/tmp}/code-metrics}" ALLOWED_FILES='^(code-files\.svg|code-lines\.svg|coverage\.svg|code-metrics\.json)$' COMMIT_MESSAGE="Update code metrics badges [skip ci]" fail() { echo "::error::[configuration] $1" exit 1 } relative_path() { case "$1" in "" | /* | .. | ../* | */.. | */../*) return 1 ;; esac } validate_inputs() { set -u relative_path "$WORKING_DIRECTORY" || fail "working-directory must be a relative path inside the repository." [ -d "$WORKING_DIRECTORY" ] || fail "working-directory '$WORKING_DIRECTORY' does not exist." if [ -n "$COVERAGE_REPORT" ]; then relative_path "$COVERAGE_REPORT" || fail "coverage-report must be a relative path inside the repository." fi if [ -n "$COVERAGE_ARTIFACT" ] && [ -z "$COVERAGE_REPORT" ]; then fail "coverage-artifact '$COVERAGE_ARTIFACT' needs coverage-report, the file inside the artifact." fi case "$BADGES_BRANCH" in main | master | HEAD | "") fail "badges-branch '$BADGES_BRANCH' must not be a default branch." ;; esac git check-ref-format --branch "$BADGES_BRANCH" > /dev/null 2>&1 || fail "badges-branch '$BADGES_BRANCH' is not a valid branch name." echo "Counting ${WORKING_DIRECTORY}; badges go to the branch ${BADGES_BRANCH}" } measure() { set -eu metrics="$(mktemp)" python3 "$TOOLS_DIR/count_loc.py" \ --directory "$WORKING_DIRECTORY" --exclude "$EXCLUDE" --output "$metrics" rm -rf "$OUT_DIR" report="$COVERAGE_REPORT" if [ -n "$COVERAGE_ARTIFACT" ]; then report="coverage-artifact/$COVERAGE_REPORT" fi python3 "$TOOLS_DIR/badges.py" \ --metrics "$metrics" --output-dir "$OUT_DIR" \ --coverage-report "$report" \ --coverage-warn "$COVERAGE_WARN" --coverage-good "$COVERAGE_GOOD" rm -f "$metrics" } # Copy the badge files into the worktree and stage them; return 0 when # something differs from the branch. stage_files() { local worktree="$1" file for file in "$OUT_DIR"/*; do cp "$file" "$worktree/$(basename "$file")" done git -C "$worktree" add -- . ! git -C "$worktree" diff --cached --quiet } commit_files() { git -C "$1" -c user.name="code-metrics" -c user.email="code-metrics@invalid" \ commit --quiet -m "$COMMIT_MESSAGE" } publish() { set -eu [ -d "$OUT_DIR" ] || fail "no badge files in '$OUT_DIR'; run measure first." local worktree exists=0 committed=0 attempt worktree="$(mktemp -d)" if git fetch --quiet origin "+refs/heads/$BADGES_BRANCH:refs/remotes/origin/$BADGES_BRANCH" 2> /dev/null; then exists=1 if git ls-tree -r --name-only "origin/$BADGES_BRANCH" | grep -qvE "$ALLOWED_FILES"; then fail "branch '$BADGES_BRANCH' holds files that are not badge files; it is not a badges branch." fi git worktree add --quiet --detach "$worktree" "origin/$BADGES_BRANCH" else git worktree add --quiet --detach "$worktree" HEAD git -C "$worktree" checkout --quiet --orphan "$BADGES_BRANCH" git -C "$worktree" rm -rfq --ignore-unmatch . fi for attempt in 1 2; do if [ "$committed" -eq 0 ]; then if stage_files "$worktree"; then commit_files "$worktree" committed=1 elif [ "$exists" -eq 1 ]; then echo "Badges are current; nothing to commit." break fi fi if git -C "$worktree" push --quiet origin "HEAD:refs/heads/$BADGES_BRANCH"; then echo "Published the badges to the branch ${BADGES_BRANCH}." break fi [ "$attempt" -lt 2 ] || fail "the push to '$BADGES_BRANCH' failed twice." if git fetch --quiet origin "+refs/heads/$BADGES_BRANCH:refs/remotes/origin/$BADGES_BRANCH" 2> /dev/null; then git -C "$worktree" reset --quiet --hard "origin/$BADGES_BRANCH" exists=1 committed=0 fi done git worktree remove --force "$worktree" } case "${1:-}" in validate) validate_inputs ;; measure) measure ;; publish) publish ;; *) echo "::error::[configuration] usage: code-metrics.sh validate|measure|publish" >&2 exit 2 ;; esac