#!/bin/bash # Git OpenTimestamp Pre-Commit Backfill Hook (ots CLI version) # Requires: opentimestamps-client (pipx install opentimestamps-client) set -e OUTPUT_DIR=".ots" STATUS_CACHE="$OUTPUT_DIR/.attestation-cache" mkdir -p "$OUTPUT_DIR" # Initialize cache if [ ! -f "$STATUS_CACHE" ]; then echo "# Format: commit-hash:status:timestamp" > "$STATUS_CACHE" fi 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) if [ "$((now - timestamp))" -lt 3600 ]; then echo "$status" return 0 fi fi return 1 } cache_status() { local commit="$1" local status="$2" echo "$commit:$status:$(date +%s)" >> "$STATUS_CACHE" } 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 fi rm -f "$temp_file" return 1 } is_attested() { local proof_file="$1" local pending_count=$(ots info "$proof_file" 2>&1 | grep -c "PendingAttestation" || echo "0") [ "$pending_count" -eq 0 ] } upgrade_proof() { local proof_file="$1" ots upgrade "$proof_file" 2>/dev/null } echo "[ots] Backfilling proofs..." 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" if [ $CURRENT -le 3 ] || [ $CURRENT -eq $TOTAL ]; then echo "[ots] Processing $CURRENT/$TOTAL: ${COMMIT:0:8}" elif [ $CURRENT -eq 4 ]; then echo "[ots] ... processing remaining ..." fi if [ -f "$PROOF_FILE" ]; then CACHED_STATUS=$(get_cached_status "$COMMIT" || echo "") [ "$CACHED_STATUS" = "attested" ] && continue if is_attested "$PROOF_FILE"; then cache_status "$COMMIT" "attested" continue fi cache_status "$COMMIT" "pending" CACHE_LINE=$(grep "^$COMMIT:" "$STATUS_CACHE" | tail -1) CACHE_TIME=$(echo "$CACHE_LINE" | cut -d: -f3) CACHE_AGE=$(($(date +%s) - CACHE_TIME)) [ "$CACHE_AGE" -lt 600 ] && continue if upgrade_proof "$PROOF_FILE"; then cache_status "$COMMIT" "attested" UPDATED=$((UPDATED + 1)) fi else if generate_proof "$COMMIT" "$PROOF_FILE"; then cache_status "$COMMIT" "pending" UPDATED=$((UPDATED + 1)) fi fi done # Update latest proof LATEST_COMMIT=$(git rev-parse HEAD) [ -f "$OUTPUT_DIR/${LATEST_COMMIT}.ots" ] && cp "$OUTPUT_DIR/${LATEST_COMMIT}.ots" "$OUTPUT_DIR/proof.ots" # 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 "") [ -n "$PREV" ] && echo "$COMMIT:$PREV" >> "$OUTPUT_DIR/commit-chain.txt" done echo "[ots] Backfill complete: $UPDATED updated"