Skip to content
Open
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
5 changes: 5 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@ ssvsigner-golangci-lint:
deadcode-lint:
./scripts/deadcode.sh

.PHONY: install-hooks
install-hooks:
git config core.hooksPath scripts/git-hooks
@echo "git hooks installed (core.hooksPath -> scripts/git-hooks)"

.PHONY: ssvsigner-boundary-lint
ssvsigner-boundary-lint:
./scripts/ssvsigner_boundary.sh
Expand Down
145 changes: 145 additions & 0 deletions scripts/git-hooks/pre-push
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
#!/bin/sh
# Pre-push hook: lint what this push introduces or modifies, so lint failures
# surface locally instead of on the PR's lint check. Two stages:
#
# 1. misspell (locale US, same as .golangci.yaml's misspell config) on the
# changed .go/.md blobs at the pushed commit.
# 2. golangci-lint (the same config CI's `make lint` uses) on the packages
# containing changed .go files. Unlike misspell it needs full packages on
# disk for type-checking, so it lints the checkout rather than the pushed
# blobs and only runs when pushing the checked-out HEAD.
#
# Install with: make install-hooks
# Bypass for a one-off push: git push --no-verify

root=$(git rev-parse --show-toplevel) || exit 0

# go tool compiles the linters from tool.mod on first use and caches them; go
# may not be on PATH in GUI-spawned shells, so fall back to the default install.
if command -v go >/dev/null 2>&1; then
GO=go
elif [ -x /usr/local/go/bin/go ]; then
GO=/usr/local/go/bin/go
else
exit 0
fi

ZERO=0000000000000000000000000000000000000000
status=0

# Changed paths may contain spaces or glob characters: iterate $files with a
# newline-only IFS (restored to the default around the outer read, which needs
# it to split its fields) and keep pathname expansion off for the whole run.
set -f
nl='
'
oIFS=$IFS
head_sha=$(git rev-parse HEAD)
lint_dirs=""
ssvsigner_lint_dirs=""
misspell_ok="" # probed lazily on the first push with changed files

while read -r local_ref local_sha remote_ref remote_sha; do
[ "$local_sha" = "$ZERO" ] && continue # deleting a remote ref

if [ "$remote_sha" = "$ZERO" ]; then
# New remote branch: diff against the merge-base with stage (or main).
base=$(git merge-base "$local_sha" origin/stage 2>/dev/null) ||
base=$(git merge-base "$local_sha" origin/main 2>/dev/null) || {
echo "pre-push: no merge-base with origin/stage or origin/main — skipping lint for $local_ref" >&2
continue
}
else
base=$remote_sha
fi

files=$(git diff --name-only --diff-filter=ACMR "$base" "$local_sha" -- '*.go' '*.md')
[ -z "$files" ] && continue

# go tool failures (resolution, compile, module download) must not be
# mistaken for spelling findings, so probe misspell once up front; if it
# can't run, warn and skip this stage instead of blocking the push.
if [ -z "$misspell_ok" ]; then
if printf '' | GOWORK=off "$GO" tool -modfile="$root/tool.mod" misspell -locale US -error >/dev/null 2>&1; then
misspell_ok=y
else
misspell_ok=n
echo "pre-push: cannot run misspell via 'go tool' — skipping the spelling stage" >&2
fi
fi

# Check the blobs at local_sha, not the working tree, so uncommitted edits
# don't mask (or fake) findings in what's actually being pushed.
if [ "$misspell_ok" = y ]; then
IFS=$nl
for f in $files; do
out=$(git show "$local_sha:$f" 2>/dev/null |
GOWORK=off "$GO" tool -modfile="$root/tool.mod" misspell -locale US -error 2>&1)
if [ -n "$out" ]; then
echo "pre-push: misspell found issues in $f:" >&2
echo "$out" | sed "s|^stdin| $f|" >&2
status=1
fi
done
IFS=$oIFS
fi

# Collect the packages holding changed .go files for the golangci-lint
# stage. Only when this ref is the checked-out HEAD — the checkout is what
# gets linted, so for any other ref the results wouldn't describe the push.
[ "$local_sha" = "$head_sha" ] || continue
IFS=$nl
for f in $files; do
case $f in
*.go) ;;
*) continue ;;
esac
d=./$(dirname "$f")
[ -d "$root/${d#./}" ] || continue # dir may be gone (renames)
case $d in
# ssvsigner is a nested module CI lints too (make lint runs
# ssvsigner-golangci-lint from ssvsigner/), and its packages can't be
# loaded from the root, so collect its dirs for a separate run.
./ssvsigner | ./ssvsigner/*)
sd=.${d#./ssvsigner}
case " $ssvsigner_lint_dirs " in
*" $sd "*) ;;
*) ssvsigner_lint_dirs="$ssvsigner_lint_dirs $sd" ;;
esac
continue
;;
# Nested module CI's `make lint` doesn't cover.
./scripts/differ | ./scripts/differ/*) continue ;;
esac
case " $lint_dirs " in
*" $d "*) ;;
*) lint_dirs="$lint_dirs $d" ;;
esac
done
IFS=$oIFS
done

# golangci-lint stage: staticcheck & friends on the changed packages, scoped so
# a push costs seconds, not CI's full-repo minutes. It sees the working tree,
# so uncommitted edits in these packages show up here.
if [ -n "$lint_dirs" ]; then
# shellcheck disable=SC2086 # lint_dirs is word-split on purpose
if ! (cd "$root" && GOWORK=off "$GO" tool -modfile="$root/tool.mod" golangci-lint run $lint_dirs >&2); then
status=1
fi
fi
if [ -n "$ssvsigner_lint_dirs" ]; then
# Mirrors make lint's ssvsigner-golangci-lint: the nested module is linted
# from its own directory, with the root's config and tool.mod.
# shellcheck disable=SC2086 # ssvsigner_lint_dirs is word-split on purpose
if ! (cd "$root/ssvsigner" && GOWORK=off "$GO" tool -modfile="$root/tool.mod" golangci-lint run -c "$root/.golangci.yaml" $ssvsigner_lint_dirs >&2); then
status=1
fi
fi

if [ "$status" -ne 0 ]; then
echo "" >&2
echo "pre-push: fix the issues above and amend/commit — the CI lint check (make lint) fails on them." >&2
echo "pre-push: bypass once with: git push --no-verify" >&2
fi
exit $status
1 change: 1 addition & 0 deletions tool.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ tool (
github.com/ethereum/go-ethereum/cmd/abigen
github.com/ferranbt/fastssz/sszgen
github.com/golangci/golangci-lint/v2/cmd/golangci-lint
github.com/golangci/misspell/cmd/misspell
github.com/swaggo/swag/cmd/swag
go.uber.org/mock/mockgen
golang.org/x/tools/cmd/deadcode
Expand Down