Scans a directory tree for git repos and produces a concise report showing uncommitted changes (with +/- character counts) and ahead/behind status per remote. Supports --dryrun to preview discovered repos without running checks. Includes OpenSpec, implementation, and 26-assertion test suite. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
242 lines
7.5 KiB
Bash
Executable File
242 lines
7.5 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Test script for git-status-report using --dryrun and live checks
|
|
set -euo pipefail
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
SCRIPT="$REPO_ROOT/scripts/git-status-report"
|
|
PASS=0
|
|
FAIL=0
|
|
TMPDIR=""
|
|
|
|
# --- Colours ---
|
|
RED='\033[31m'
|
|
GREEN='\033[32m'
|
|
RESET='\033[0m'
|
|
|
|
cleanup() {
|
|
[[ -n "$TMPDIR" && -d "$TMPDIR" ]] && rm -rf "$TMPDIR"
|
|
}
|
|
trap cleanup EXIT
|
|
|
|
setup_tmpdir() {
|
|
TMPDIR=$(mktemp -d)
|
|
}
|
|
|
|
pass() {
|
|
printf " ${GREEN}✓${RESET} %s\n" "$1"
|
|
PASS=$((PASS + 1))
|
|
}
|
|
|
|
fail() {
|
|
printf " ${RED}✗${RESET} %s\n" "$1"
|
|
[[ -n "${2:-}" ]] && printf " %s\n" "$2"
|
|
FAIL=$((FAIL + 1))
|
|
}
|
|
|
|
assert_contains() {
|
|
local output="$1" expected="$2" label="$3"
|
|
if echo "$output" | grep -qF -- "$expected"; then
|
|
pass "$label"
|
|
else
|
|
fail "$label" "Expected to find: $expected"
|
|
fi
|
|
}
|
|
|
|
assert_not_contains() {
|
|
local output="$1" unexpected="$2" label="$3"
|
|
if echo "$output" | grep -qF -- "$unexpected"; then
|
|
fail "$label" "Did not expect to find: $unexpected"
|
|
else
|
|
pass "$label"
|
|
fi
|
|
}
|
|
|
|
assert_exit_code() {
|
|
local actual="$1" expected="$2" label="$3"
|
|
if [[ "$actual" -eq "$expected" ]]; then
|
|
pass "$label"
|
|
else
|
|
fail "$label" "Expected exit $expected, got $actual"
|
|
fi
|
|
}
|
|
|
|
# ============================================================
|
|
echo "=== git-status-report tests ==="
|
|
echo ""
|
|
|
|
# --- Test: --help flag ---
|
|
echo "-- help flag --"
|
|
output=$(bash "$SCRIPT" --help 2>&1) || true
|
|
assert_contains "$output" "Usage:" "--help shows usage"
|
|
assert_contains "$output" "--dryrun" "--help mentions dryrun"
|
|
|
|
# --- Test: no repos found ---
|
|
echo "-- no repos found --"
|
|
setup_tmpdir
|
|
output=$(bash "$SCRIPT" "$TMPDIR" 2>&1) || true
|
|
assert_contains "$output" "No git repositories found" "empty dir reports no repos"
|
|
|
|
# --- Test: dryrun discovers repos ---
|
|
echo "-- dryrun discovery --"
|
|
setup_tmpdir
|
|
mkdir -p "$TMPDIR/repo-a"
|
|
git -C "$TMPDIR/repo-a" init -q
|
|
mkdir -p "$TMPDIR/subdir/repo-b"
|
|
git -C "$TMPDIR/subdir/repo-b" init -q
|
|
|
|
output=$(bash "$SCRIPT" --dryrun "$TMPDIR" 2>&1)
|
|
rc=$?
|
|
assert_exit_code "$rc" 0 "dryrun exits 0"
|
|
assert_contains "$output" "[dryrun] Would check 2 repositories:" "dryrun header with count"
|
|
assert_contains "$output" "repo-a" "dryrun lists repo-a"
|
|
assert_contains "$output" "repo-b" "dryrun lists repo-b"
|
|
|
|
# --- Test: dryrun with -n short flag ---
|
|
echo "-- dryrun short flag --"
|
|
output=$(bash "$SCRIPT" -n "$TMPDIR" 2>&1)
|
|
assert_contains "$output" "[dryrun]" "-n flag works as dryrun"
|
|
|
|
# --- Test: clean repo ---
|
|
echo "-- clean repo --"
|
|
setup_tmpdir
|
|
mkdir -p "$TMPDIR/clean-repo"
|
|
git -C "$TMPDIR/clean-repo" init -q
|
|
git -C "$TMPDIR/clean-repo" commit --allow-empty -m "init" -q
|
|
|
|
output=$(bash "$SCRIPT" "$TMPDIR" 2>&1)
|
|
rc=$?
|
|
assert_exit_code "$rc" 0 "clean repo exits 0"
|
|
assert_contains "$output" "clean and in sync" "clean repo shows clean message"
|
|
|
|
# --- Test: dirty repo with uncommitted changes ---
|
|
echo "-- dirty repo --"
|
|
setup_tmpdir
|
|
mkdir -p "$TMPDIR/dirty-repo"
|
|
git -C "$TMPDIR/dirty-repo" init -q
|
|
echo "hello world" > "$TMPDIR/dirty-repo/file.txt"
|
|
git -C "$TMPDIR/dirty-repo" add file.txt
|
|
git -C "$TMPDIR/dirty-repo" commit -m "init" -q
|
|
echo "hello world modified with extra content" > "$TMPDIR/dirty-repo/file.txt"
|
|
|
|
output=$(bash "$SCRIPT" "$TMPDIR" 2>&1) && rc=$? || rc=$?
|
|
assert_exit_code "$rc" 1 "dirty repo exits 1"
|
|
assert_contains "$output" "dirty: dirty-repo" "dirty repo header shown"
|
|
assert_contains "$output" "file.txt" "changed file listed"
|
|
|
|
# --- Test: untracked files ---
|
|
echo "-- untracked files --"
|
|
setup_tmpdir
|
|
mkdir -p "$TMPDIR/untracked-repo"
|
|
git -C "$TMPDIR/untracked-repo" init -q
|
|
git -C "$TMPDIR/untracked-repo" commit --allow-empty -m "init" -q
|
|
echo "new file content" > "$TMPDIR/untracked-repo/newfile.txt"
|
|
|
|
output=$(bash "$SCRIPT" "$TMPDIR" 2>&1) && rc=$? || rc=$?
|
|
assert_exit_code "$rc" 1 "untracked file makes repo dirty"
|
|
assert_contains "$output" "newfile.txt" "untracked file listed"
|
|
assert_contains "$output" "??" "untracked status shown"
|
|
|
|
# --- Test: deleted files ---
|
|
echo "-- deleted files --"
|
|
setup_tmpdir
|
|
mkdir -p "$TMPDIR/del-repo"
|
|
git -C "$TMPDIR/del-repo" init -q
|
|
echo "content to delete" > "$TMPDIR/del-repo/gone.txt"
|
|
git -C "$TMPDIR/del-repo" add gone.txt
|
|
git -C "$TMPDIR/del-repo" commit -m "init" -q
|
|
rm "$TMPDIR/del-repo/gone.txt"
|
|
|
|
output=$(bash "$SCRIPT" "$TMPDIR" 2>&1) || true
|
|
assert_contains "$output" "gone.txt" "deleted file listed"
|
|
|
|
# --- Test: repo with no remotes ---
|
|
echo "-- no remotes --"
|
|
setup_tmpdir
|
|
mkdir -p "$TMPDIR/no-remote"
|
|
git -C "$TMPDIR/no-remote" init -q
|
|
echo "change" > "$TMPDIR/no-remote/file.txt"
|
|
git -C "$TMPDIR/no-remote" add file.txt
|
|
git -C "$TMPDIR/no-remote" commit -m "init" -q
|
|
echo "modified" > "$TMPDIR/no-remote/file.txt"
|
|
|
|
output=$(bash "$SCRIPT" "$TMPDIR" 2>&1) || true
|
|
assert_contains "$output" "no remotes" "no remotes noted on dirty repo"
|
|
|
|
# --- Test: ahead of remote ---
|
|
echo "-- ahead of remote --"
|
|
setup_tmpdir
|
|
# Create a bare "remote"
|
|
git init -q --bare "$TMPDIR/remote.git"
|
|
mkdir -p "$TMPDIR/ahead-repo"
|
|
git -C "$TMPDIR/ahead-repo" init -q
|
|
git -C "$TMPDIR/ahead-repo" remote add origin "$TMPDIR/remote.git"
|
|
echo "first" > "$TMPDIR/ahead-repo/file.txt"
|
|
git -C "$TMPDIR/ahead-repo" add file.txt
|
|
git -C "$TMPDIR/ahead-repo" commit -m "first" -q
|
|
git -C "$TMPDIR/ahead-repo" push -u origin main -q 2>/dev/null || git -C "$TMPDIR/ahead-repo" push -u origin master -q 2>/dev/null
|
|
# Make a local-only commit
|
|
echo "second" > "$TMPDIR/ahead-repo/file.txt"
|
|
git -C "$TMPDIR/ahead-repo" add file.txt
|
|
git -C "$TMPDIR/ahead-repo" commit -m "second" -q
|
|
|
|
output=$(bash "$SCRIPT" "$TMPDIR/ahead-repo" 2>&1) || true
|
|
assert_contains "$output" "1 ahead" "ahead count shown"
|
|
|
|
# --- Test: mixed clean and dirty ---
|
|
echo "-- mixed repos --"
|
|
setup_tmpdir
|
|
mkdir -p "$TMPDIR/clean"
|
|
git -C "$TMPDIR/clean" init -q
|
|
git -C "$TMPDIR/clean" commit --allow-empty -m "init" -q
|
|
|
|
mkdir -p "$TMPDIR/dirty"
|
|
git -C "$TMPDIR/dirty" init -q
|
|
echo "content" > "$TMPDIR/dirty/file.txt"
|
|
git -C "$TMPDIR/dirty" add file.txt
|
|
git -C "$TMPDIR/dirty" commit -m "init" -q
|
|
echo "changed" > "$TMPDIR/dirty/file.txt"
|
|
|
|
output=$(bash "$SCRIPT" "$TMPDIR" 2>&1) || true
|
|
assert_contains "$output" "Scanned 2 repositories" "scanned count correct"
|
|
assert_contains "$output" "dirty: dirty" "dirty repo listed"
|
|
assert_contains "$output" "1 repositories are clean" "clean count shown"
|
|
|
|
# --- Test: nested repos not double-counted ---
|
|
echo "-- nested repos --"
|
|
setup_tmpdir
|
|
mkdir -p "$TMPDIR/outer"
|
|
git -C "$TMPDIR/outer" init -q
|
|
git -C "$TMPDIR/outer" commit --allow-empty -m "init" -q
|
|
mkdir -p "$TMPDIR/outer/inner"
|
|
git -C "$TMPDIR/outer/inner" init -q
|
|
git -C "$TMPDIR/outer/inner" commit --allow-empty -m "init" -q
|
|
|
|
output=$(bash "$SCRIPT" --dryrun "$TMPDIR" 2>&1)
|
|
assert_contains "$output" "Would check 1 repositories:" "nested repo filtered out"
|
|
assert_contains "$output" "outer" "outer repo found"
|
|
assert_not_contains "$output" "inner" "inner repo excluded"
|
|
|
|
# --- Test: permission denied ---
|
|
echo "-- permission denied --"
|
|
setup_tmpdir
|
|
mkdir -p "$TMPDIR/accessible"
|
|
git -C "$TMPDIR/accessible" init -q
|
|
git -C "$TMPDIR/accessible" commit --allow-empty -m "init" -q
|
|
mkdir -p "$TMPDIR/blocked"
|
|
chmod 000 "$TMPDIR/blocked"
|
|
|
|
output=$(bash "$SCRIPT" "$TMPDIR" 2>&1)
|
|
rc=$?
|
|
# Should still find the accessible repo
|
|
assert_contains "$output" "Scanned 1 repositories" "survives permission denied"
|
|
chmod 755 "$TMPDIR/blocked" # Cleanup
|
|
|
|
# ============================================================
|
|
echo ""
|
|
echo "Results: $PASS passed, $FAIL failed"
|
|
if [[ $FAIL -gt 0 ]]; then
|
|
exit 1
|
|
fi
|
|
printf "${GREEN}All tests passed.${RESET}\n"
|