diff --git a/scripts/diff-configs.sh b/scripts/diff-configs.sh index 778d165..424ea85 100755 --- a/scripts/diff-configs.sh +++ b/scripts/diff-configs.sh @@ -14,6 +14,102 @@ BLUE='\033[0;34m' YELLOW='\033[0;33m' NC='\033[0m' # No Color +# Normalize nix store paths by replacing 32-char hashes with placeholder +normalize_nix_paths() { + sed -E 's|/nix/store/[a-z0-9]{32}-|/nix/store/HASH-|g' +} + +# Filter diff output to remove hunks where only nix store hashes differ +# Returns: filtered diff (empty if only hash changes), exit code 0 if real changes found +filter_hash_only_diffs() { + local diff_output="$1" + local current_hunk="" + local output="" + local has_real_changes=false + + # Process line by line + while IFS= read -r line || [ -n "$line" ]; do + if [[ "$line" =~ ^@@ ]]; then + # New hunk starts - process previous one if it exists + if [ -n "$current_hunk" ]; then + if hunk_has_real_changes "$current_hunk"; then + output+="$current_hunk"$'\n' + has_real_changes=true + fi + fi + # Start new hunk + current_hunk="$line"$'\n' + else + # Add line to current hunk + current_hunk+="$line"$'\n' + fi + done <<< "$diff_output" + + # Process last hunk + if [ -n "$current_hunk" ]; then + if hunk_has_real_changes "$current_hunk"; then + output+="$current_hunk" + has_real_changes=true + fi + fi + + # Remove trailing newline + output="${output%$'\n'}" + + if [ "$has_real_changes" = true ]; then + echo "$output" + return 0 + else + return 1 + fi +} + +# Check if a diff hunk has real changes (not just hash changes) +hunk_has_real_changes() { + local hunk="$1" + + # Use temp file to avoid bash here-string issues + local temp_hunk=$(mktemp) + printf '%s' "$hunk" > "$temp_hunk" + + local minus_lines=() + local plus_lines=() + + # Extract - and + lines (skip @@ and context lines) + while IFS= read -r line || [ -n "$line" ]; do + if [[ "$line" =~ ^- && ! "$line" =~ ^--- ]]; then + minus_lines+=("${line:1}") # Remove the - prefix + elif [[ "$line" =~ ^\+ && ! "$line" =~ ^\+\+\+ ]]; then + plus_lines+=("${line:1}") # Remove the + prefix + fi + done < "$temp_hunk" + + rm -f "$temp_hunk" + + # If counts don't match, there are structural changes + if [ ${#minus_lines[@]} -ne ${#plus_lines[@]} ]; then + return 0 # Has real changes + fi + + # If no changes at all, skip + if [ ${#minus_lines[@]} -eq 0 ]; then + return 1 # No real changes + fi + + # Compare each pair of lines after normalization + for i in "${!minus_lines[@]}"; do + local minus_norm=$(echo "${minus_lines[$i]}" | normalize_nix_paths) + local plus_norm=$(echo "${plus_lines[$i]}" | normalize_nix_paths) + + if [ "$minus_norm" != "$plus_norm" ]; then + return 0 # Has real changes + fi + done + + # All changes are hash-only + return 1 +} + # Check for nvd if ! command -v nvd &> /dev/null; then echo "Error: nvd not found in PATH" @@ -205,11 +301,17 @@ for host in $hosts; do echo echo -e " ${BLUE}▸ $basename${NC}" - # If it's a directory, diff key files within it + # If it's a directory, diff all files within it if [ -d "$old_path" ] && [ -d "$new_path" ]; then - # Focus on important files - for pattern in "activate" "etc/*" "*.conf" "*.fish" "*.service" "*.nix"; do - while IFS= read -r file; do + # Count files to avoid processing huge directories + file_count=$(find "$new_path" -maxdepth 3 -type f 2>/dev/null | wc -l) + + # Skip very large directories (e.g., system-path with 900+ files) + if [ "$file_count" -gt 100 ]; then + echo " (skipping directory with $file_count files - too large)" + else + # Diff all files in the directory + for file in $(find "$new_path" -maxdepth 3 -type f 2>/dev/null); do [ -z "$file" ] && continue relpath="${file#$new_path/}" old_file="$old_path/$relpath" @@ -217,16 +319,38 @@ for host in $hosts; do if [ -f "$old_file" ] && [ -f "$file" ]; then # Check if file is text if file "$file" | grep -q "text"; then - echo -e " ${YELLOW}$relpath:${NC}" - diff -u "$old_file" "$file" 2>/dev/null | head -50 | tail -n +3 | sed 's/^/ /' || true + # Get diff output + diff_output=$(diff -u "$old_file" "$file" 2>/dev/null | head -50 | tail -n +3 || true) + + # Filter hash-only changes + if [ -n "$diff_output" ]; then + filtered_diff=$(filter_hash_only_diffs "$diff_output" || true) + + if [ -n "$filtered_diff" ]; then + echo -e " ${YELLOW}$relpath:${NC}" + echo "$filtered_diff" | sed 's/^/ /' + fi + fi fi fi - done < <(find "$new_path" -type f -name "$pattern" 2>/dev/null | head -20) - done + done + fi # If it's a file, diff it directly elif [ -f "$old_path" ] && [ -f "$new_path" ]; then if file "$new_path" | grep -q "text"; then - diff -u "$old_path" "$new_path" 2>/dev/null | head -50 | tail -n +3 | sed 's/^/ /' || true + # Get diff output + diff_output=$(diff -u "$old_path" "$new_path" 2>/dev/null | head -50 | tail -n +3 || true) + + # Filter hash-only changes + if [ -n "$diff_output" ]; then + filtered_diff=$(filter_hash_only_diffs "$diff_output" || true) + + if [ -n "$filtered_diff" ]; then + echo "$filtered_diff" | sed 's/^/ /' + else + echo " (only hash changes)" + fi + fi else echo " (binary file)" fi