diff --git a/CLAUDE.md b/CLAUDE.md index 1d473d8cc5a18..e9bfccfb7d147 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -34,6 +34,12 @@ Never regenerate the entire Cargo.lock. When adding or changing dependencies: * **Never run bare `cargo update`** — it bumps every semver-compatible dep in the workspace, causing unrelated breakage from transitive dependency changes. * **If the lock file was regenerated**, diff it before committing (`git diff Cargo.lock | grep '^[+-]version'`) and pin back any unintended bumps with `cargo update -p --precise `. +To catch a forgotten `Cargo.lock` commit before CI does (saves ~15 min per push), install the pre-push hook once per clone: + +```sh +ln -sf ../../bin/git-hook-pre-push .git/hooks/pre-push +``` + ### Licensing Two files control license policy and **must be kept in sync**: `deny.toml` (`[licenses].allow`) and `about.toml` (`accepted`). When a new dependency introduces a license not already allowed, add the SPDX identifier to both files. diff --git a/bin/git-hook-pre-push b/bin/git-hook-pre-push new file mode 100755 index 0000000000000..00f1ba15ad3a8 --- /dev/null +++ b/bin/git-hook-pre-push @@ -0,0 +1,57 @@ +#!/usr/bin/env bash + +# Copyright Materialize, Inc. and contributors. All rights reserved. +# +# Use of this software is governed by the Business Source License +# included in the LICENSE file at the root of this repository. +# +# As of the Change Date specified in that file, in accordance with +# the Business Source License, use of this software will be governed +# by the Apache License, Version 2.0. +# +# git-hook-pre-push — fast checks for the CI failures that waste the most time. +# +# Install: +# ln -sf ../../bin/git-hook-pre-push .git/hooks/pre-push +# +# Currently checks: +# - Cargo.lock is in sync with Cargo.toml files (catches the common case +# where a workspace dep was added but Cargo.lock wasn't committed). +# +# Bypass (emergencies only): git push --no-verify + +set -euo pipefail + +cd "$(git rev-parse --show-toplevel)" + +cargo_changed=false +while read -r _ local_sha _ remote_sha; do + if [ "$remote_sha" = "0000000000000000000000000000000000000000" ]; then + remote_sha="$(git merge-base HEAD origin/main 2>/dev/null || echo HEAD~1)" + fi + if git diff --name-only "$remote_sha" "$local_sha" 2>/dev/null \ + | grep -qE '(^|/)Cargo\.(toml|lock)$'; then + cargo_changed=true + break + fi +done + +if [ "$cargo_changed" = false ]; then + exit 0 +fi + +if ! command -v cargo &>/dev/null; then + exit 0 +fi + +if ! cargo metadata --locked --offline --format-version 1 >/dev/null 2>&1; then + echo >&2 "" + echo >&2 "pre-push: Cargo.lock is out of sync with Cargo.toml." + echo >&2 "" + echo >&2 " Run: cargo check" + echo >&2 " Then: git add Cargo.lock && git commit" + echo >&2 "" + echo >&2 "Bypass (if you know what you're doing): git push --no-verify" + echo >&2 "" + exit 1 +fi