'Generic Commit'
Some checks failed
Build-Test-Publish / build (push) Has been cancelled

This commit is contained in:
Your Name 2025-06-15 21:00:52 +12:00
parent 1362f03462
commit 0622b250cc
2 changed files with 212 additions and 152 deletions

View File

@ -66,13 +66,17 @@ fi
# Test 2: Running with a valid directory should save config
echo -e "\nTest 2: First run with directory argument"
TEST_DIR="/home/j/code"
# Use a directory that should exist in most environments
TEST_DIR="/tmp"
if [ -d "$TEST_DIR" ]; then
"$WHATSDIRTY_SCRIPT" "$TEST_DIR" > /dev/null 2>&1
if [ -f "$CONFIG_FILE" ] && [ "$(cat "$CONFIG_FILE")" = "$TEST_DIR" ]; then
print_test "Directory saved to config" "PASS"
else
print_test "Directory saved to config" "FAIL"
echo " Expected config to contain: $TEST_DIR"
echo " Config file exists: $([ -f "$CONFIG_FILE" ] && echo "yes" || echo "no")"
[ -f "$CONFIG_FILE" ] && echo " Config contains: $(cat "$CONFIG_FILE")"
fi
else
print_test "Directory saved to config" "SKIP (test directory not found)"
@ -80,10 +84,13 @@ fi
# Test 3: Running without arguments should use saved directory
echo -e "\nTest 3: Subsequent run without arguments"
if "$WHATSDIRTY_SCRIPT" > /dev/null 2>&1; then
OUTPUT=$("$WHATSDIRTY_SCRIPT" 2>&1 || true)
# Should not contain error message about no directory
if ! echo "$OUTPUT" | grep -q "Error:" && [ -f "$CONFIG_FILE" ]; then
print_test "Uses saved directory from config" "PASS"
else
print_test "Uses saved directory from config" "FAIL"
echo " Output: $OUTPUT"
fi
# Test 4: Running with new directory should update config

View File

@ -1,178 +1,231 @@
#!/bin/bash
# iterate through all subfolders in the current directory (or $1 if provided),
# and check:
# is it a git rep?
# if so is it clean (nothing to commit)?
#
# for each git repo print the name of the repo, the datestamp of the last commit,
# the nubmer of changes since then, and whether it's clean or not.
# whatsdirty.sh - Check git repository status in subdirectories
# Shows which repositories have uncommitted changes and when they were last committed
# Configuration file path
# Configuration
CONFIG_FILE="$HOME/.config/whatsdirty.conf"
RED='\033[0;31m'
NC='\033[0m' # No Color
# === Configuration Management Functions ===
# Function to save directory to config
save_directory() {
local dir="$1"
mkdir -p "$(dirname "$CONFIG_FILE")"
echo "$dir" > "$CONFIG_FILE"
}
# Function to load directory from config
load_directory() {
if [ -f "$CONFIG_FILE" ]; then
cat "$CONFIG_FILE"
fi
}
# Determine which directory to use
if [ $# -eq 0 ]; then
# No argument provided - try to load from config
SAVED_DIR=$(load_directory)
if [ -z "$SAVED_DIR" ]; then
echo "Error: No directory specified and no saved directory found."
echo "Usage: $0 <directory>"
echo "The specified directory will be saved for future use."
exit 1
fi
DIR="$SAVED_DIR"
else
# Argument provided - use it and save to config
DIR="$1"
# Validate and canonicalize the directory first
DIR=$(cd "$DIR" 2>/dev/null && pwd) || { echo "Error: Directory '$1' not found"; exit 1; }
save_directory "$DIR"
fi
# Make sure directory is canonical (in case loaded from config)
DIR=$(cd "$DIR" 2>/dev/null && pwd) || {
echo "Error: Saved directory '$DIR' no longer exists";
rm -f "$CONFIG_FILE"
exit 1;
validate_directory() {
local dir="$1"
# Use subshell to avoid changing current directory
(cd "$dir" 2>/dev/null && pwd)
}
# Color codes
RED='\033[0;31m'
NC='\033[0m' # No Color
# Temporary file to store repository data
TEMP_FILE=$(mktemp)
# shellcheck disable=SC2064
trap "rm -f $TEMP_FILE" EXIT
# iterate through all subfolders in DIR
for folder in "$DIR"/*; do
# Skip if not a directory
[ ! -d "$folder" ] && continue
handle_directory_argument() {
local arg="$1"
# Check if it's a git repository
if [ -d "$folder/.git" ]; then
# Get the folder name
repo_name=$(basename "$folder")
# Change to the repository directory
cd "$folder" || continue
# Get the last commit timestamp in seconds since epoch
last_commit_timestamp=$(git log -1 --format="%ct" 2>/dev/null)
if [ -n "$last_commit_timestamp" ]; then
# Get current timestamp
current_timestamp=$(date +%s)
# Calculate difference in seconds
diff_seconds=$((current_timestamp - last_commit_timestamp))
# Convert to hours with decimal precision
# Using bc for floating point arithmetic
hours_ago=$(echo "scale=1; $diff_seconds / 3600" | bc)
# Ensure leading zero for values less than 1
if [[ $hours_ago =~ ^\. ]]; then
hours_ago="0$hours_ago"
fi
# Format the hours number and "hours" label separately
hours_num="$hours_ago"
hours_label="hours"
# Combine with right-justified formatting
hours_formatted=$(printf "%8s %-6s" "$hours_num" "$hours_label")
else
hours_num="999999" # Large number for sorting
hours_label="commits"
hours_formatted=$(printf "%8s %-7s" "No" "$hours_label")
if [ -z "$arg" ]; then
# No argument - try to load from config
local saved_dir=$(load_directory)
if [ -z "$saved_dir" ]; then
echo "Error: No directory specified and no saved directory found." >&2
echo "Usage: $0 <directory>" >&2
echo "The specified directory will be saved for future use." >&2
exit 1
fi
# Count uncommitted changes (staged and unstaged)
# Count modified, deleted, and untracked files
staged_changes=$(git diff --cached --numstat 2>/dev/null | wc -l)
unstaged_changes=$(git diff --numstat 2>/dev/null | wc -l)
untracked_files=$(git ls-files --others --exclude-standard 2>/dev/null | wc -l)
total_changes=$((staged_changes + unstaged_changes + untracked_files))
# Store repo data with status prefix (D for dirty, C for clean)
if [ $total_changes -eq 0 ]; then
echo "C|${hours_num}|${total_changes}|${repo_name}|${hours_formatted}" >> "$TEMP_FILE"
else
# For dirty repos, collect the list of changed files
changed_files=""
# Get staged files
staged_files=$(git diff --cached --name-only 2>/dev/null)
if [ -n "$staged_files" ]; then
while IFS= read -r file; do
changed_files="${changed_files} [staged] $file\n"
done <<< "$staged_files"
fi
# Get unstaged modified files
unstaged_files=$(git diff --name-only 2>/dev/null)
if [ -n "$unstaged_files" ]; then
while IFS= read -r file; do
changed_files="${changed_files} [modified] $file\n"
done <<< "$unstaged_files"
fi
# Get untracked files
untracked=$(git ls-files --others --exclude-standard 2>/dev/null)
if [ -n "$untracked" ]; then
while IFS= read -r file; do
changed_files="${changed_files} [untracked] $file\n"
done <<< "$untracked"
fi
# Remove trailing newline
changed_files=$(echo -e "$changed_files" | sed '$ d')
# Store with a special delimiter (|||) to separate file list
echo "D|${hours_num}|${total_changes}|${repo_name}|${hours_formatted}|||${changed_files}" >> "$TEMP_FILE"
echo "$saved_dir"
else
# Argument provided - validate and save
local canonical_dir=$(validate_directory "$arg")
if [ -z "$canonical_dir" ]; then
echo "Error: Directory '$arg' not found" >&2
exit 1
fi
# Return to the original directory
cd "$DIR" || exit 1
save_directory "$canonical_dir"
echo "$canonical_dir"
fi
done
}
# Print header
printf "%-40s %15s %-10s %-10s\n" "Repository" "Last Commit" "Changes" "Status"
printf "%-40s %15s %-10s %-10s\n" "----------" "(hours)" "-------" "------"
# === Git Repository Functions ===
# Print dirty repositories first (sorted by changes, descending)
if grep -q "^D|" "$TEMP_FILE" 2>/dev/null; then
printf "\n=== DIRTY REPOSITORIES ===\n\n"
grep "^D|" "$TEMP_FILE" | sort -t'|' -k3,3nr | while IFS='|' read -r status hours changes repo hours_fmt separator file_list; do
printf "%-40s %15s ${RED}%-10s${NC} ${RED}%-10s${NC}\n" "$repo" "$hours_fmt" "$changes" "Dirty"
# Print the changed files if they exist
if [ -n "$file_list" ]; then
echo -e "$file_list"
get_last_commit_hours() {
local last_commit_timestamp=$(git log -1 --format="%ct" 2>/dev/null)
if [ -n "$last_commit_timestamp" ]; then
local current_timestamp=$(date +%s)
local diff_seconds=$((current_timestamp - last_commit_timestamp))
# Convert to hours with decimal precision
local hours_ago=$(echo "scale=1; $diff_seconds / 3600" | bc)
# Ensure leading zero for values less than 1
if [[ $hours_ago =~ ^\. ]]; then
hours_ago="0$hours_ago"
fi
echo "$hours_ago"
else
echo ""
fi
}
count_git_changes() {
local staged=$(git diff --cached --numstat 2>/dev/null | wc -l)
local unstaged=$(git diff --numstat 2>/dev/null | wc -l)
local untracked=$(git ls-files --others --exclude-standard 2>/dev/null | wc -l)
echo $((staged + unstaged + untracked))
}
get_changed_files_list() {
local changed_files=""
# Get staged files
local staged_files=$(git diff --cached --name-only 2>/dev/null)
if [ -n "$staged_files" ]; then
while IFS= read -r file; do
changed_files="${changed_files} [staged] $file\n"
done <<< "$staged_files"
fi
# Get unstaged modified files
local unstaged_files=$(git diff --name-only 2>/dev/null)
if [ -n "$unstaged_files" ]; then
while IFS= read -r file; do
changed_files="${changed_files} [modified] $file\n"
done <<< "$unstaged_files"
fi
# Get untracked files
local untracked=$(git ls-files --others --exclude-standard 2>/dev/null)
if [ -n "$untracked" ]; then
while IFS= read -r file; do
changed_files="${changed_files} [untracked] $file\n"
done <<< "$untracked"
fi
# Remove trailing newline
if [ -n "$changed_files" ]; then
echo -e "$changed_files" | sed '$ d'
fi
}
analyze_repository() {
local repo_path="$1"
local repo_name=$(basename "$repo_path")
cd "$repo_path" || return 1
# Get commit info
local hours_ago=$(get_last_commit_hours)
local hours_num
local hours_label
local hours_formatted
if [ -n "$hours_ago" ]; then
hours_num="$hours_ago"
hours_label="hours"
hours_formatted=$(printf "%8s %-6s" "$hours_num" "$hours_label")
else
hours_num="999999" # Large number for sorting
hours_label="commits"
hours_formatted=$(printf "%8s %-7s" "No" "$hours_label")
fi
# Count changes
local total_changes=$(count_git_changes)
# Determine status and collect data
if [ $total_changes -eq 0 ]; then
echo "C|${hours_num}|${total_changes}|${repo_name}|${hours_formatted}"
else
local changed_files=$(get_changed_files_list)
echo "D|${hours_num}|${total_changes}|${repo_name}|${hours_formatted}|||${changed_files}"
fi
}
# === Output Formatting Functions ===
print_header() {
printf "%-40s %15s %-10s %-10s\n" "Repository" "Last Commit" "Changes" "Status"
printf "%-40s %15s %-10s %-10s\n" "----------" "(hours)" "-------" "------"
}
print_dirty_repositories() {
local temp_file="$1"
if grep -q "^D|" "$temp_file" 2>/dev/null; then
printf "\n=== DIRTY REPOSITORIES ===\n\n"
grep "^D|" "$temp_file" | sort -t'|' -k3,3nr | while IFS='|' read -r status hours changes repo hours_fmt separator file_list; do
printf "%-40s %15s ${RED}%-10s${NC} ${RED}%-10s${NC}\n" "$repo" "$hours_fmt" "$changes" "Dirty"
if [ -n "$file_list" ]; then
echo -e "$file_list"
fi
done
fi
}
print_clean_repositories() {
local temp_file="$1"
if grep -q "^C|" "$temp_file" 2>/dev/null; then
printf "\n=== CLEAN REPOSITORIES ===\n\n"
grep "^C|" "$temp_file" | sort -t'|' -k2,2n | while IFS='|' read -r status hours changes repo hours_fmt; do
printf "%-40s %15s %-10s %-10s\n" "$repo" "$hours_fmt" "$changes" "Clean"
done
fi
}
# === Main Function ===
scan_repositories() {
local scan_dir="$1"
local temp_file="$2"
for folder in "$scan_dir"/*; do
[ ! -d "$folder" ] && continue
if [ -d "$folder/.git" ]; then
local repo_data=$(analyze_repository "$folder")
if [ -n "$repo_data" ]; then
echo "$repo_data" >> "$temp_file"
fi
cd "$scan_dir" || exit 1
fi
done
fi
}
# Print clean repositories (sorted by hours, ascending)
if grep -q "^C|" "$TEMP_FILE" 2>/dev/null; then
printf "\n=== CLEAN REPOSITORIES ===\n\n"
grep "^C|" "$TEMP_FILE" | sort -t'|' -k2,2n | while IFS='|' read -r status hours changes repo hours_fmt; do
printf "%-40s %15s %-10s %-10s\n" "$repo" "$hours_fmt" "$changes" "Clean"
done
fi
main() {
# Handle directory argument
local DIR=$(handle_directory_argument "$1")
# Validate directory still exists (for loaded configs)
local validated_dir=$(validate_directory "$DIR")
if [ -z "$validated_dir" ]; then
echo "Error: Saved directory '$DIR' no longer exists" >&2
rm -f "$CONFIG_FILE"
exit 1
fi
DIR="$validated_dir"
# Create temp file for results
local TEMP_FILE=$(mktemp)
trap "rm -f $TEMP_FILE" EXIT
# Scan repositories
scan_repositories "$DIR" "$TEMP_FILE"
# Display results
print_header
print_dirty_repositories "$TEMP_FILE"
print_clean_repositories "$TEMP_FILE"
}
# Execute main function with all arguments
main "$@"