-
Notifications
You must be signed in to change notification settings - Fork 139
Expand file tree
/
Copy pathpre-push
More file actions
executable file
·219 lines (185 loc) · 7.78 KB
/
pre-push
File metadata and controls
executable file
·219 lines (185 loc) · 7.78 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
#!/usr/bin/env bash
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright the Vortex contributors
#
# Git Pre-Push Hook for Vortex
# Validates commits being pushed to ensure code quality and DCO (signed-off commits).
#
# If you want to use this hook, simply copy and paste the file into a new file `.git/hooks/pre-push`
# and everything should work correctly. If you are in the project root, you can use these command:
#
# ```sh
# cp scripts/pre-push .git/hooks/
# ```
set -Euo pipefail
# Configuration.
MAIN_BRANCH="develop" # The main branch name for vortex.
# Color codes for terminal output.
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
BOLD='\033[1m'
RESET='\033[0m'
# Output helper functions.
print_error() {
echo -e "${RED}$1${RESET}"
}
print_success() {
echo -e "${GREEN}$1${RESET}"
}
print_warning() {
echo -e "${YELLOW}$1${RESET}"
}
print_info() {
echo -e "${BLUE}$1${RESET}"
}
print_header() {
echo -e "\n${BOLD}$1${RESET}"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
}
# Navigate to repository root to ensure consistent execution.
ROOT_DIR="$(git rev-parse --show-toplevel)"
cd "$ROOT_DIR"
# Parse stdin from git to determine what commits are being pushed.
# Git provides information about each ref being pushed via stdin.
SKIP=true
COMMIT_RANGES=()
print_header "🚀 Pre-push checks starting..."
# Process each ref being pushed. Git provides one line per ref via stdin.
# Each line contains the local ref, local SHA, remote ref, and remote SHA.
while read LOCAL_REF LOCAL_SHA REMOTE_REF REMOTE_SHA; do
# Check if this is an actual push (not a branch deletion).
if [[ "$LOCAL_REF" != "(delete)" && \
"$LOCAL_SHA" != "0000000000000000000000000000000000000000" ]]; then
SKIP=false
# Determine the range of commits to check.
# We only want to check new commits that aren't already on the remote.
if [ "$REMOTE_SHA" = "0000000000000000000000000000000000000000" ]; then
# This is a new branch being pushed for the first time.
# Check only commits since diverging from the main branch.
if git show-ref --verify --quiet "refs/remotes/origin/$MAIN_BRANCH"; then
MERGE_BASE=$(git merge-base "origin/$MAIN_BRANCH" "$LOCAL_SHA" 2>/dev/null || echo "")
if [ -n "$MERGE_BASE" ]; then
RANGE="$MERGE_BASE..$LOCAL_SHA"
else
# Can't find merge base, check all commits in the branch.
RANGE="origin/$MAIN_BRANCH..$LOCAL_SHA"
fi
else
# Main branch doesn't exist remotely yet (rare case).
print_warning "⚠️ Remote branch 'origin/$MAIN_BRANCH' not found."
RANGE="$LOCAL_SHA"
fi
else
# This is an existing branch being updated.
# Only check the new commits being pushed.
RANGE="$REMOTE_SHA..$LOCAL_SHA"
fi
COMMIT_RANGES+=("$RANGE")
# Show what we're checking for transparency.
NUM_COMMITS=$(git rev-list --count "$RANGE" 2>/dev/null || echo "0")
if [ "$NUM_COMMITS" -gt 0 ]; then
print_info "ℹ️ Checking $NUM_COMMITS new commit(s)"
fi
fi
done
# Exit early if only deleting branches.
if $SKIP; then
print_info "ℹ️ Only deleting remote branches - skipping checks."
exit 0
fi
# ============================================================================
# CHECK 1: Commit Sign-offs (DCO Compliance)
# ============================================================================
# Verify that all commits have proper Signed-off-by trailers for DCO compliance.
# This ensures contributors have agreed to the Developer Certificate of Origin.
print_header "✍️ Checking commit sign-offs (DCO compliance)"
UNSIGNED_COMMITS=()
TOTAL_COMMITS=0
SIGNED_COMMITS=0
# Iterate through each commit range identified during the push.
# Each range contains only the new commits that will be pushed to the remote.
for RANGE in "${COMMIT_RANGES[@]}"; do
# Skip empty ranges.
if ! git rev-list -n1 "$RANGE" &>/dev/null; then
continue
fi
# Process each individual commit in this range to check for DCO compliance.
# Extract commit hash, subject, committer name, and email for validation.
while IFS='|' read -r COMMIT SUBJECT NAME EMAIL; do
TOTAL_COMMITS=$((TOTAL_COMMITS + 1))
COMMIT_SHORT="${COMMIT:0:7}"
# Extract commit message and trailers.
FULL_MESSAGE=$(git show -s --format=%B "$COMMIT")
TRAILERS=$(echo "$FULL_MESSAGE" | git interpret-trailers --parse)
if [ -z "$TRAILERS" ]; then
# No trailers found at all.
UNSIGNED_COMMITS+=(" ${RED}✗${RESET} ${COMMIT_SHORT} ${SUBJECT}")
UNSIGNED_COMMITS+=(" ${YELLOW}→ Missing Signed-off-by trailer${RESET}")
else
# Check for Signed-off-by trailer.
SIGNOFF=$(echo "$TRAILERS" | grep "^Signed-off-by: " || true)
if [ -z "$SIGNOFF" ]; then
# Has trailers but no Signed-off-by.
UNSIGNED_COMMITS+=(" ${RED}✗${RESET} ${COMMIT_SHORT} ${SUBJECT}")
UNSIGNED_COMMITS+=(" ${YELLOW}→ Has trailers but missing Signed-off-by${RESET}")
else
# Accept any valid Signed-off-by trailer.
SIGNED_COMMITS=$((SIGNED_COMMITS + 1))
fi
fi
done < <(git rev-list --format='%H|%s|%cn|%ce' --no-commit-header "$RANGE")
done
# Report DCO check results.
if [ ${#UNSIGNED_COMMITS[@]} -gt 0 ]; then
print_error "\n❌ Missing sign-offs found:"
printf '%s\n' "${UNSIGNED_COMMITS[@]}"
echo
print_info "💡 Add sign-offs with: git commit --signoff or git rebase --signoff"
echo
print_warning "⚠️ To skip checks: git push --no-verify"
exit 1
else
if [ "$TOTAL_COMMITS" -gt 0 ]; then
print_success "✅ All $TOTAL_COMMITS commit(s) properly signed off"
fi
fi
# ============================================================================
# CHECK 2: Clippy Lints
# ============================================================================
# Run Clippy to catch common mistakes and enforce idiomatic Rust patterns.
# This helps maintain code quality and prevents potential bugs.
print_header "📝 Running Clippy linter"
if ! cargo clippy --version &> /dev/null; then
print_warning "⚠️ Clippy not found - skipping lint check."
echo " Install with: rustup component add clippy"
else
# Run Clippy with warnings as errors.
CLIPPY_OUTPUT=$(cargo clippy --all-targets --all-features -- -D warnings 2>&1)
CLIPPY_EXIT_CODE=$?
if [ $CLIPPY_EXIT_CODE -eq 0 ]; then
print_success "✅ Clippy found no issues"
else
print_error "❌ Clippy found problems"
echo
# Show first 25 lines of errors.
echo "$CLIPPY_OUTPUT" | head -25
if [ $(echo "$CLIPPY_OUTPUT" | wc -l) -gt 25 ]; then
echo
echo "... (output truncated, run 'cargo clippy' for full output)"
fi
echo
print_warning "⚠️ To skip checks: git push --no-verify"
exit 1
fi
fi
# ============================================================================
# Success
# ============================================================================
echo
print_success "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
print_success "🎉 All pre-push checks passed! Proceeding with push..."
print_success "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo
exit 0