Created
June 28, 2025 23:49
-
-
Save tmm1/0ec42a8a12bf78ece7a43ec6204cbdc3 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
# `retimer` saves modification times (mtimes) of source files, and restores them only if the | |
# contents match a hash. | |
# | |
# `retimer`'s intended workflow is as follows: | |
# | |
# 1. restore `.retimer-state` and `target` from CI cache, if available | |
# 2. `retimer restore` | |
# 3. `cargo build` | |
# 4. `retimer save` | |
# 5. save `.retimer-state` and `target` in CI cache for the next run | |
# | |
# This allows `cargo`'s mtime-based caching to work properly with CI/git. | |
# | |
# Based on scottlamb's https://github.com/rust-lang/cargo/issues/6529#issuecomment-1558438048 | |
set -euo pipefail | |
RETIMER_STATE_FILE=".retimer-state" | |
# Function to compute SHA256 hash of a file | |
compute_hash() { | |
local file="$1" | |
if command -v sha256sum >/dev/null 2>&1; then | |
sha256sum "$file" | cut -d' ' -f1 | |
elif command -v shasum >/dev/null 2>&1; then | |
shasum -a 256 "$file" | cut -d' ' -f1 | |
else | |
echo "Error: Neither sha256sum nor shasum found" >&2 | |
exit 1 | |
fi | |
} | |
# Function to get modification time in seconds since epoch | |
get_mtime() { | |
local file="$1" | |
if [[ "$OSTYPE" == "darwin"* ]]; then | |
stat -f %m "$file" | |
else | |
stat -c %.Y "$file" | |
fi | |
} | |
# Function to set modification time | |
set_mtime() { | |
local file="$1" | |
local mtime="$2" | |
# Try different approaches based on OS | |
if [[ "$OSTYPE" == "darwin"* ]]; then | |
# macOS: use date -r with timestamp | |
touch -t "$(date -r "$mtime" +%Y%m%d%H%M.%S)" "$file" 2>/dev/null || \ | |
touch -d "@$mtime" "$file" 2>/dev/null || { | |
echo "Warning: Could not set mtime for $file" >&2 | |
return 1 | |
} | |
else | |
touch -d "@$mtime" "$file" 2>/dev/null || { | |
echo "Warning: Could not set mtime for $file" >&2 | |
return 1 | |
} | |
fi | |
} | |
# Function to save current state | |
save_state() { | |
echo "Saving retimer state..." | |
local temp_file="${RETIMER_STATE_FILE}.tmp" | |
# Create temporary file | |
> "$temp_file" | |
# Find all src files and save their hash and mtime | |
find . -name "*.rs" -type f -not -path "./target/*" | while IFS= read -r file; do | |
if [[ -r "$file" ]]; then | |
local hash | |
local mtime | |
hash=$(compute_hash "$file") | |
mtime=$(get_mtime "$file") | |
printf "%s\t%s\t%s\n" "$file" "$hash" "$mtime" >> "$temp_file" | |
fi | |
done | |
# Atomically replace the state file | |
mv "$temp_file" "$RETIMER_STATE_FILE" | |
local count | |
count=$(wc -l < "$RETIMER_STATE_FILE") | |
echo "Saved state for $count files" | |
} | |
# Function to restore state | |
restore_state() { | |
if [[ ! -f "$RETIMER_STATE_FILE" ]]; then | |
echo "No retimer state file found, skipping restore" | |
return 0 | |
fi | |
echo "Restoring retimer state..." | |
local restored=0 | |
local skipped=0 | |
local total=0 | |
while IFS= read -r line; do | |
total=$((total + 1)) | |
# Parse tab-separated values manually to avoid shell escaping issues | |
local file hash saved_mtime | |
file=$(echo "$line" | cut -f1) | |
hash=$(echo "$line" | cut -f2) | |
saved_mtime=$(echo "$line" | cut -f3) | |
# Skip empty lines or malformed entries | |
if [[ -z "$file" || -z "$hash" || -z "$saved_mtime" ]]; then | |
echo "Warning: Malformed line in state file, skipping" | |
skipped=$((skipped + 1)) | |
continue | |
fi | |
if [[ ! -f "$file" ]]; then | |
echo "Warning: File $file no longer exists, skipping" | |
skipped=$((skipped + 1)) | |
continue | |
fi | |
if [[ ! -r "$file" ]]; then | |
echo "Warning: Cannot read $file, skipping" | |
skipped=$((skipped + 1)) | |
continue | |
fi | |
local current_hash | |
current_hash=$(compute_hash "$file") | |
if [[ "$current_hash" == "$hash" ]]; then | |
if set_mtime "$file" "$saved_mtime"; then | |
restored=$((restored + 1)) | |
else | |
echo "Warning: Failed to set mtime for $file" | |
skipped=$((skipped + 1)) | |
fi | |
else | |
# File content has changed, don't restore mtime | |
echo "Skipping $file (content changed: $current_hash vs $hash)" | |
skipped=$((skipped + 1)) | |
fi | |
done < "$RETIMER_STATE_FILE" | |
echo "Restored $restored files, skipped $skipped files (out of $total total)" | |
} | |
# Function to show usage | |
usage() { | |
echo "Usage: $0 {save|restore}" | |
echo " save - Save current mtimes and hashes of .rs files" | |
echo " restore - Restore mtimes for files whose content hasn't changed" | |
exit 1 | |
} | |
# Main script logic | |
case "${1:-}" in | |
save) | |
save_state | |
;; | |
restore) | |
restore_state | |
;; | |
*) | |
usage | |
;; | |
esac |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment