git-ots/hooks/pre-commit

156 lines
4.2 KiB
Bash
Executable file

#!/bin/bash
# Git OpenTimestamp Pre-Commit Backfill Hook
# Self-contained - no external dependencies
# Upgrades all historical proofs before each new commit
set -e
# Configuration
OUTPUT_DIR=".ots"
STATUS_CACHE="$OUTPUT_DIR/.attestation-cache"
# Ensure output directory exists
mkdir -p "$OUTPUT_DIR"
# Initialize cache file if it doesn't exist
if [ ! -f "$STATUS_CACHE" ]; then
echo "# Attestation status cache" > "$STATUS_CACHE"
echo "# Format: commit-hash:status:timestamp" >> "$STATUS_CACHE"
fi
# Function to get cached status
get_cached_status() {
local commit="$1"
local cache_line=$(grep "^$commit:" "$STATUS_CACHE" 2>/dev/null | tail -1)
if [ -n "$cache_line" ]; then
local status=$(echo "$cache_line" | cut -d: -f2)
local timestamp=$(echo "$cache_line" | cut -d: -f3)
local now=$(date +%s)
local age=$((now - timestamp))
# Cache valid for 1 hour (3600 seconds)
if [ "$age" -lt 3600 ]; then
echo "$status"
return 0
fi
fi
return 1
}
# Function to cache status
cache_status() {
local commit="$1"
local status="$2"
local now=$(date +%s)
echo "$commit:$status:$now" >> "$STATUS_CACHE"
}
# Function to generate proof using ots CLI
generate_proof() {
local hash="$1"
local output="$2"
local temp_file=$(mktemp)
local temp_ots="${temp_file}.ots"
python3 -c "import sys; sys.stdout.buffer.write(bytes.fromhex('$hash'))" > "$temp_file"
ots stamp "$temp_file" 2>/dev/null
if [ -f "$temp_ots" ]; then
mv "$temp_ots" "$output"
rm -f "$temp_file"
return 0
else
rm -f "$temp_file"
return 1
fi
}
# Function to check if proof is attested
is_attested() {
local proof_file="$1"
local pending_count=$(ots info "$proof_file" 2>&1 | grep -c "PendingAttestation" || echo "0")
[ "$pending_count" -eq 0 ]
}
# Function to upgrade proof
upgrade_proof() {
local proof_file="$1"
ots upgrade "$proof_file" 2>/dev/null
}
echo "[ots] Backfilling proofs..."
# Get all commit hashes (oldest to newest)
COMMITS=$(git rev-list --reverse HEAD)
TOTAL=$(echo "$COMMITS" | wc -l)
CURRENT=0
UPDATED=0
for COMMIT in $COMMITS; do
CURRENT=$((CURRENT + 1))
PROOF_FILE="$OUTPUT_DIR/${COMMIT}.ots"
# Skip verbose output for brevity
if [ $CURRENT -le 3 ] || [ $CURRENT -eq $TOTAL ]; then
echo "[ots] Processing commit $CURRENT/$TOTAL: ${COMMIT:0:8}"
elif [ $CURRENT -eq 4 ]; then
echo "[ots] ... processing remaining commits ..."
fi
if [ -f "$PROOF_FILE" ]; then
# Check cached status first
CACHED_STATUS=$(get_cached_status "$COMMIT" || echo "")
if [ "$CACHED_STATUS" = "attested" ]; then
continue
fi
# Check if already attested
if is_attested "$PROOF_FILE"; then
cache_status "$COMMIT" "attested"
continue
fi
# Cache as pending
cache_status "$COMMIT" "pending"
# Skip upgrade if cache is fresh (< 10 min old)
CACHE_LINE=$(grep "^$COMMIT:" "$STATUS_CACHE" | tail -1)
CACHE_TIME=$(echo "$CACHE_LINE" | cut -d: -f3)
NOW=$(date +%s)
CACHE_AGE=$((NOW - CACHE_TIME))
if [ "$CACHE_AGE" -lt 600 ]; then
continue
fi
# Try to upgrade
if upgrade_proof "$PROOF_FILE"; then
cache_status "$COMMIT" "attested"
UPDATED=$((UPDATED + 1))
fi
else
# Generate new proof
if generate_proof "$COMMIT" "$PROOF_FILE"; then
cache_status "$COMMIT" "pending"
UPDATED=$((UPDATED + 1))
fi
fi
done
# Update latest proof symlink
LATEST_COMMIT=$(git rev-parse HEAD)
if [ -f "$OUTPUT_DIR/${LATEST_COMMIT}.ots" ]; then
cp "$OUTPUT_DIR/${LATEST_COMMIT}.ots" "$OUTPUT_DIR/proof.ots"
fi
# Save commit chain
rm -f "$OUTPUT_DIR/commit-chain.txt"
git rev-list HEAD | while read COMMIT; do
PREV=$(git rev-parse ${COMMIT}^1 2>/dev/null || echo "")
if [ -n "$PREV" ]; then
echo "$COMMIT:$PREV" >> "$OUTPUT_DIR/commit-chain.txt"
fi
done
echo "[ots] Backfill complete: $UPDATED proofs updated"