Skip to content

Instantly share code, notes, and snippets.

@tmm1
Created June 28, 2025 23:49
Show Gist options
  • Save tmm1/0ec42a8a12bf78ece7a43ec6204cbdc3 to your computer and use it in GitHub Desktop.
Save tmm1/0ec42a8a12bf78ece7a43ec6204cbdc3 to your computer and use it in GitHub Desktop.
#!/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