test: Add 8 files
This commit is contained in:
21
install.sh
Executable file
21
install.sh
Executable file
@@ -0,0 +1,21 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
#SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
|
||||
|
||||
echo "Installing whatsdirty"
|
||||
|
||||
if ! command -v getpkg ; then
|
||||
|
||||
ARCH=$(uname -m)
|
||||
wget "https://getbin.xyz/getpkg:latest-${ARCH}" -O bootstrap && chmod a+x bootstrap
|
||||
./bootstrap install getpkg
|
||||
rm ./bootstrap
|
||||
VERSION=$(getpkg version)
|
||||
echo "Dropshell tool $VERSION installed"
|
||||
|
||||
fi
|
||||
|
||||
getpkg install whatsdirty
|
||||
|
5
setup_script.sh
Executable file
5
setup_script.sh
Executable file
@@ -0,0 +1,5 @@
|
||||
#!/bin/bash
|
||||
|
||||
echo "whatsdirty tool setup completed successfully!"
|
||||
echo "Use 'whatsdirty <directory>' to scan git repositories for uncommitted changes."
|
||||
echo "The specified directory will be saved for future use."
|
167
test.sh
Executable file
167
test.sh
Executable file
@@ -0,0 +1,167 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
|
||||
WHATSDIRTY_SCRIPT="$SCRIPT_DIR/whatsdirty"
|
||||
CONFIG_FILE="$HOME/.config/whatsdirty.conf"
|
||||
BACKUP_FILE=""
|
||||
TEST_PASSED=true
|
||||
|
||||
# Colors for output
|
||||
GREEN='\033[0;32m'
|
||||
RED='\033[0;31m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Function to print test results
|
||||
print_test() {
|
||||
local test_name="$1"
|
||||
local result="$2"
|
||||
if [ "$result" = "PASS" ]; then
|
||||
echo -e "${GREEN}✓${NC} $test_name"
|
||||
else
|
||||
echo -e "${RED}✗${NC} $test_name"
|
||||
TEST_PASSED=false
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to cleanup and restore
|
||||
CLEANED=false
|
||||
cleanup() {
|
||||
if [ "$CLEANED" = true ]; then
|
||||
return
|
||||
fi
|
||||
CLEANED=true
|
||||
if [ -n "$BACKUP_FILE" ] && [ -f "$BACKUP_FILE" ]; then
|
||||
cp "$BACKUP_FILE" "$CONFIG_FILE"
|
||||
rm -f "$BACKUP_FILE"
|
||||
elif [ -n "$BACKUP_FILE" ]; then
|
||||
# Original didn't exist, so remove the created one
|
||||
rm -f "$CONFIG_FILE"
|
||||
fi
|
||||
}
|
||||
|
||||
# Set up trap to restore config on exit
|
||||
trap cleanup EXIT
|
||||
|
||||
echo "Testing whatsdirty configuration behavior..."
|
||||
echo "Working directory: $(pwd)"
|
||||
echo "Script path: $WHATSDIRTY_SCRIPT"
|
||||
echo "Config file: $CONFIG_FILE"
|
||||
echo
|
||||
|
||||
# Backup existing config if it exists
|
||||
if [ -f "$CONFIG_FILE" ]; then
|
||||
BACKUP_FILE=$(mktemp)
|
||||
cp "$CONFIG_FILE" "$BACKUP_FILE"
|
||||
else
|
||||
# Mark that we need to remove the config file on cleanup
|
||||
BACKUP_FILE="NONE_EXISTED"
|
||||
fi
|
||||
|
||||
# Remove config to start fresh
|
||||
rm -f "$CONFIG_FILE"
|
||||
|
||||
# Test 1: Running without arguments and no config should fail
|
||||
echo "Test 1: First run without arguments (should fail)"
|
||||
OUTPUT=$("$WHATSDIRTY_SCRIPT" 2>&1 || true)
|
||||
if echo "$OUTPUT" | grep -q "No directory specified and no saved directory found"; then
|
||||
print_test "First run without args shows error" "PASS"
|
||||
else
|
||||
print_test "First run without args shows error" "FAIL"
|
||||
echo " Expected: 'No directory specified and no saved directory found'"
|
||||
echo " Got: $OUTPUT"
|
||||
fi
|
||||
|
||||
# Test 2: Running with a valid directory should save config
|
||||
echo -e "\nTest 2: First run with directory argument"
|
||||
# Use a directory that should exist in most environments
|
||||
TEST_DIR="/tmp"
|
||||
echo " Testing with directory: $TEST_DIR"
|
||||
if [ -d "$TEST_DIR" ]; then
|
||||
echo " Directory exists, running script..."
|
||||
SCRIPT_OUTPUT=$("$WHATSDIRTY_SCRIPT" "$TEST_DIR" 2>&1)
|
||||
SCRIPT_EXIT_CODE=$?
|
||||
echo " Script exit code: $SCRIPT_EXIT_CODE"
|
||||
if [ $SCRIPT_EXIT_CODE -eq 0 ] && [ -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")"
|
||||
echo " Script output: $SCRIPT_OUTPUT"
|
||||
fi
|
||||
else
|
||||
print_test "Directory saved to config" "SKIP (test directory not found)"
|
||||
fi
|
||||
|
||||
# Test 3: Running without arguments should use saved directory
|
||||
echo -e "\nTest 3: Subsequent run without arguments"
|
||||
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
|
||||
echo -e "\nTest 4: Run with different directory"
|
||||
# Use the parent directory (should exist)
|
||||
NEW_TEST_DIR="$SCRIPT_DIR/.."
|
||||
if [ -d "$NEW_TEST_DIR" ]; then
|
||||
CANONICAL_DIR=$(cd "$NEW_TEST_DIR" && pwd)
|
||||
"$WHATSDIRTY_SCRIPT" "$NEW_TEST_DIR" > /dev/null 2>&1
|
||||
if [ -f "$CONFIG_FILE" ] && [ "$(cat "$CONFIG_FILE")" = "$CANONICAL_DIR" ]; then
|
||||
print_test "Config updated with new directory" "PASS"
|
||||
else
|
||||
print_test "Config updated with new directory" "FAIL"
|
||||
echo " Expected: $CANONICAL_DIR"
|
||||
echo " Got: $(cat "$CONFIG_FILE" 2>/dev/null || echo "file not found")"
|
||||
fi
|
||||
else
|
||||
print_test "Config updated with new directory" "SKIP (test directory not found)"
|
||||
fi
|
||||
|
||||
# Test 5: Invalid directory should fail
|
||||
echo -e "\nTest 5: Invalid directory handling"
|
||||
OUTPUT=$("$WHATSDIRTY_SCRIPT" "/nonexistent/directory" 2>&1 || true)
|
||||
if echo "$OUTPUT" | grep -q "Directory '/nonexistent/directory' not found"; then
|
||||
print_test "Invalid directory shows error" "PASS"
|
||||
else
|
||||
print_test "Invalid directory shows error" "FAIL"
|
||||
echo " Expected: 'Directory '/nonexistent/directory' not found'"
|
||||
echo " Got: $OUTPUT"
|
||||
fi
|
||||
|
||||
# Test 6: Saved directory that no longer exists
|
||||
echo -e "\nTest 6: Saved directory that no longer exists"
|
||||
mkdir -p "$(dirname "$CONFIG_FILE")"
|
||||
echo "/nonexistent/saved/directory" > "$CONFIG_FILE"
|
||||
OUTPUT=$("$WHATSDIRTY_SCRIPT" 2>&1 || true)
|
||||
if echo "$OUTPUT" | grep -q "no longer exists"; then
|
||||
# Check that config was removed
|
||||
if [ ! -f "$CONFIG_FILE" ]; then
|
||||
print_test "Handles deleted saved directory" "PASS"
|
||||
else
|
||||
print_test "Handles deleted saved directory" "FAIL (config not cleaned up)"
|
||||
fi
|
||||
else
|
||||
print_test "Handles deleted saved directory" "FAIL"
|
||||
echo " Expected message about directory no longer existing"
|
||||
echo " Got: $OUTPUT"
|
||||
fi
|
||||
|
||||
cleanup
|
||||
|
||||
# Summary
|
||||
echo
|
||||
if [ "$TEST_PASSED" = true ]; then
|
||||
echo -e "${GREEN}All tests passed!${NC}"
|
||||
exit 0
|
||||
else
|
||||
echo -e "${RED}Some tests failed!${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
335
whatsdirty
Executable file
335
whatsdirty
Executable file
@@ -0,0 +1,335 @@
|
||||
#!/bin/bash
|
||||
|
||||
# whatsdirty.sh - Check git repository status in subdirectories
|
||||
# Shows which repositories have uncommitted changes and when they were last committed
|
||||
|
||||
# Configuration
|
||||
CONFIG_FILE="$HOME/.config/whatsdirty.conf"
|
||||
RED='\033[0;31m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# === Configuration Management Functions ===
|
||||
|
||||
save_directory() {
|
||||
local dir="$1"
|
||||
mkdir -p "$(dirname "$CONFIG_FILE")"
|
||||
echo "$dir" > "$CONFIG_FILE"
|
||||
}
|
||||
|
||||
load_directory() {
|
||||
if [ -f "$CONFIG_FILE" ]; then
|
||||
cat "$CONFIG_FILE"
|
||||
fi
|
||||
}
|
||||
|
||||
validate_directory() {
|
||||
local dir="$1"
|
||||
# Use subshell to avoid changing current directory
|
||||
(cd "$dir" 2>/dev/null && pwd)
|
||||
}
|
||||
|
||||
handle_directory_argument() {
|
||||
local arg="$1"
|
||||
|
||||
if [ -z "$arg" ]; then
|
||||
# No argument - try to load from config
|
||||
local saved_dir
|
||||
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
|
||||
echo "$saved_dir"
|
||||
else
|
||||
# Argument provided - validate and save
|
||||
local canonical_dir
|
||||
canonical_dir=$(validate_directory "$arg")
|
||||
if [ -z "$canonical_dir" ]; then
|
||||
echo "Error: Directory '$arg' not found" >&2
|
||||
exit 1
|
||||
fi
|
||||
save_directory "$canonical_dir"
|
||||
echo "$canonical_dir"
|
||||
fi
|
||||
}
|
||||
|
||||
# === Git Repository Functions ===
|
||||
|
||||
get_last_commit_hours() {
|
||||
local last_commit_timestamp
|
||||
last_commit_timestamp=$(git log -1 --format="%ct" 2>/dev/null)
|
||||
|
||||
if [ -n "$last_commit_timestamp" ]; then
|
||||
local current_timestamp
|
||||
current_timestamp=$(date +%s)
|
||||
local diff_seconds=$((current_timestamp - last_commit_timestamp))
|
||||
|
||||
# Convert to hours with decimal precision using awk (more portable than bc)
|
||||
local hours_ago
|
||||
hours_ago=$(awk "BEGIN {printf \"%.1f\", $diff_seconds / 3600}")
|
||||
|
||||
# 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
|
||||
staged=$(git diff --cached --numstat 2>/dev/null | wc -l)
|
||||
local unstaged
|
||||
unstaged=$(git diff --numstat 2>/dev/null | wc -l)
|
||||
local untracked
|
||||
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
|
||||
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
|
||||
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
|
||||
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 base_dir="$2"
|
||||
local repo_name
|
||||
|
||||
# Calculate relative path from base directory
|
||||
if [ -n "$base_dir" ]; then
|
||||
repo_name=$(realpath --relative-to="$base_dir" "$repo_path" 2>/dev/null)
|
||||
# If realpath fails, fall back to basename
|
||||
if [ -z "$repo_name" ] || [ "$repo_name" = "$repo_path" ]; then
|
||||
repo_name=$(basename "$repo_path")
|
||||
fi
|
||||
else
|
||||
repo_name=$(basename "$repo_path")
|
||||
fi
|
||||
|
||||
cd "$repo_path" || return 1
|
||||
|
||||
# Get commit info
|
||||
local hours_ago
|
||||
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
|
||||
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
|
||||
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 _ _ changes repo hours_fmt _ 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 _ _ 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"
|
||||
|
||||
# Use find to recursively locate all .git directories
|
||||
while IFS= read -r git_dir; do
|
||||
# Get the repository path (parent of .git)
|
||||
local repo_path
|
||||
repo_path=$(dirname "$git_dir")
|
||||
|
||||
local repo_data
|
||||
repo_data=$(analyze_repository "$repo_path" "$scan_dir")
|
||||
if [ -n "$repo_data" ]; then
|
||||
echo "$repo_data" >> "$temp_file"
|
||||
fi
|
||||
cd "$scan_dir" || exit 1
|
||||
done < <(find "$scan_dir" -type d -name ".git" 2>/dev/null | sort)
|
||||
}
|
||||
|
||||
main() {
|
||||
# Handle directory argument
|
||||
local DIR
|
||||
DIR=$(handle_directory_argument "$1")
|
||||
|
||||
# Validate directory still exists (for loaded configs)
|
||||
local validated_dir
|
||||
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
|
||||
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"
|
||||
}
|
||||
|
||||
# === Autocomplete Function ===
|
||||
|
||||
handle_autocomplete() {
|
||||
local cur_word="${COMP_WORDS[COMP_CWORD]}"
|
||||
|
||||
# If we're completing the first argument after 'whatsdirty'
|
||||
if [ ${COMP_CWORD} -eq 1 ]; then
|
||||
# Suggest common directories and saved directory
|
||||
local saved_dir
|
||||
saved_dir=$(load_directory)
|
||||
local suggestions=""
|
||||
|
||||
# Add saved directory if it exists
|
||||
if [ -n "$saved_dir" ] && [ -d "$saved_dir" ]; then
|
||||
suggestions="$saved_dir"
|
||||
fi
|
||||
|
||||
# Add common development directories
|
||||
for dir in ~/code ~/projects ~/dev ~/src ~/workspace /home/*/code /home/*/projects; do
|
||||
if [ -d "$dir" ]; then
|
||||
# Only add if it contains git repositories
|
||||
if find "$dir" -maxdepth 2 -name ".git" -type d 2>/dev/null | head -1 | grep -q .; then
|
||||
suggestions="$suggestions $dir"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
# Directory completion for current word
|
||||
if [ -n "$cur_word" ]; then
|
||||
# Use mapfile for directory completion
|
||||
local dir_completions
|
||||
mapfile -t dir_completions < <(compgen -d -- "$cur_word")
|
||||
# Add our suggestions that match the current word
|
||||
local matching_suggestions
|
||||
mapfile -t matching_suggestions < <(echo "$suggestions" | tr ' ' '\n' | grep "^$cur_word" | sort -u)
|
||||
# Combine and deduplicate
|
||||
mapfile -t COMPREPLY < <(printf '%s\n' "${dir_completions[@]}" "${matching_suggestions[@]}" | sort -u)
|
||||
else
|
||||
# No current word, suggest all our directories plus general directory completion
|
||||
local dir_completions
|
||||
mapfile -t dir_completions < <(compgen -d)
|
||||
local all_suggestions
|
||||
mapfile -t all_suggestions < <(echo "$suggestions" | tr ' ' '\n' | sort -u)
|
||||
# Combine and deduplicate, but limit to reasonable number
|
||||
mapfile -t COMPREPLY < <(printf '%s\n' "${all_suggestions[@]}" "${dir_completions[@]}" | sort -u | head -20)
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# Check if we're being called for autocomplete
|
||||
if [ "$1" = "autocomplete" ]; then
|
||||
# This is called when the tool is being used for bash completion
|
||||
# The arguments after 'autocomplete' are the completion context
|
||||
shift
|
||||
|
||||
# Set up COMP_WORDS and COMP_CWORD from arguments
|
||||
COMP_WORDS=("$@")
|
||||
COMP_CWORD=$(($# - 1))
|
||||
|
||||
# Generate completions
|
||||
handle_autocomplete
|
||||
|
||||
# Print completions, one per line
|
||||
printf '%s\n' "${COMPREPLY[@]}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Check if we're being called to show version
|
||||
if [ "$1" = "version" ] || [ "$1" = "--version" ] || [ "$1" = "-v" ] || [ "$1" = "v" ]; then
|
||||
echo "2025.0803.0612"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Execute main function with all arguments
|
||||
main "$@"
|
Reference in New Issue
Block a user