commit 12363605cdb955059c3d41eea7ca058a161407f1
parent 1ae7753fc298125003c2182a895342907d71a7b0
Author: Fred Großkopf <fred@kuandu.systems>
Date: Tue, 28 Apr 2026 22:15:07 +0200
WIP: updates gitpages.sh
Diffstat:
| M | gitpages.sh | | | 277 | ++++++++++++++++++++++++------------------------------------------------------- |
| M | test-gitpages.sh | | | 417 | ++++++++++--------------------------------------------------------------------- |
2 files changed, 132 insertions(+), 562 deletions(-)
diff --git a/gitpages.sh b/gitpages.sh
@@ -1,240 +1,129 @@
#!/bin/sh
+# gitpages.sh - Regenerate static git pages
+# Usage: gitpages.sh [-c config] SRC
set -eu
-# === CONSTANTS ===
-
-CONFIG_FILE_NAME="gitpages.conf"
-
-usage() {
- cat << EOF
-usage: $(basename "$0") [-c CONFIG_FILE] SRC DST_DIR [ASSETS_DIR]
-
-Generates static git HTML pages + index using stagit.
-
-Arguments:
- SRC Single git repo OR directory containing *.git repos
- DST_DIR Output directory for HTML pages
- ASSETS_DIR Optional: CSS, favicon, etc. directory
-
-Options:
- -c CONFIG_FILE Path to config (default: ./$CONFIG_FILE_NAME)
- -h Show this help
-
-Examples:
- $(basename "$0") /path/to/repo.git /var/www/git
- $(basename "$0") /path/to/repos /var/www/git
- $(basename "$0") -c /etc/$CONFIG_FILE_NAME /path/to/repos /var/www/git
-EOF
+# Default configuration path
+DEFAULT_CONFIG="/etc/gitpages.conf"
+
+# --- Secure Config Parser ---
+get_config_val() {
+ awk -F '=' -v key="$1" '
+ $1 ~ /^[[:space:]]*[a-zA-Z0-9_]+[[:space:]]*$/ && $1 == key {
+ gsub(/[[:space:]]+$/, "", $2);
+ gsub(/^[[:space:]]+/, "", $2);
+ print $2;
+ exit;
+ }
+ ' "$CONFIG"
}
-# === UTILITY FUNCTIONS ===
-error() {
- printf 'error: %s\n' "$1" >&2
- exit 1
-}
-warning() { printf 'warning: %s\n' "$1" >&2; }
+# --- Utility Functions ---
+error() { printf 'error: %s\n' "$1" >&2; exit 1; }
info() { printf 'info: %s\n' "$1"; }
-
-repo_base() { basename "$1" ".git"; }
-html_dir_for() { printf '%s/%s' "$2" "$(repo_base "$1")"; }
-
sed_escape() { printf '%s\n' "$1" | sed 's/[\\/&]/\\&/g'; }
-# === VALIDATION ===
-check_args() {
- case $# in 2 | 3) ;; *) error "usage: $0 [-c CONFIG_FILE] SRC DST_DIR [ASSETS_DIR]" ;; esac
-}
-
-check_html_writable() {
- [ -d "$DST_DIR" ] || error "$DST_DIR does not exist"
- [ -w "$DST_DIR" ] || error "cannot write to $DST_DIR"
-}
-
-check_stagit() {
+# --- Validation ---
+check_deps() {
command -v stagit > /dev/null 2>&1 || error "stagit not found"
command -v stagit-index > /dev/null 2>&1 || error "stagit-index not found"
+ command -v git > /dev/null 2>&1 || error "git not found"
}
-check_assets() {
- [ -n "${ASSETS_DIR:-}" ] || return 0
- [ -d "$ASSETS_DIR" ] || error "ASSETS_DIR '$ASSETS_DIR' is not a directory"
- [ -r "$ASSETS_DIR" ] || error "cannot read ASSETS_DIR '$ASSETS_DIR'"
-
- found=0
- for f in "$ASSETS_DIR"/*; do [ -e "$f" ] && {
- found=1
- break
- }; done
- [ "$found" -eq 1 ] || warning "ASSETS_DIR '$ASSETS_DIR' is empty"
-}
-
-is_git_repo() { git -C "$1" rev-parse --git-dir > /dev/null 2>&1; }
-has_git_repos() {
- for repo in "$1"/*.git; do [ -e "$repo" ] && return 0; done
- false
-}
-
-# === CORE FUNCTIONS ===
+# --- Core Logic ---
generate_repo_html() {
- repo="$1" dst_dir="$2" repo_name="$3"
- html_repo_dir="$dst_dir/$repo_name"
-
- rm -rf "$html_repo_dir"
- mkdir -p "$html_repo_dir"
- cd "$html_repo_dir"
- stagit "$repo" > /dev/null 2>&1
+ rm -rf "$TARGET_DIR"
+ mkdir -p "$TARGET_DIR"
+ cd "$TARGET_DIR"
+ stagit "$REPO_SRC" >/dev/null 2>&1
}
process_repo_titles() {
- repo="$1" repo_name="$2" html_repo_dir="$3"
- [ -d "$html_repo_dir" ] || return 0
[ -n "${REPO_TITLE_DESC:-}" ] || return 0
-
- esc_description=$(sed_escape "$REPO_TITLE_DESC")
- find "$html_repo_dir" -name "*.html" -type f -exec sed -i \
- "s| - $repo_name - [^<]*| - $repo_name$esc_description|g" {} +
+ esc_desc=$(sed_escape "$REPO_TITLE_DESC")
+ find "$TARGET_DIR" -name "*.html" -type f -exec sed -i \
+ "s| - $REPO_BASE - [^<]*| - $REPO_BASE$esc_desc|g" {} +
}
generate_index() {
- cd "$DST_DIR"
- stagit-index "$SRC_DIR"/*.git > index.html
+ # 1. Verify destination path exists
+ [ -d "$WEB_ROOT/$GIT_HTML" ] || { printf 'Error: %s/html not found\n' "$WEB_ROOT"; return 1; }
+
+ # 2. Use a subshell to avoid persistent cd and ensure we run from the right place
+ (
+ cd "$WEB_ROOT/$GIT_HTML" || exit 1
+
+ # 3. Check if we have repositories before running the command
+ # Use 'find' to safely handle the list of repos
+ repos=$(find "$SRC_DIR" -maxdepth 1 -name "*.git")
+
+ if [ -n "$repos" ]; then
+ stagit-index $repos > index.html
+ else
+ printf 'No repositories found in %s\n' "$SRC_DIR"
+ fi
+ )
}
process_index() {
- [ -f "$DST_DIR/index.html" ] || return 0
-
+ [ -f "$WEB_ROOT/$GIT_HTML/index.html" ] || return 0
[ -n "${INDEX_TITLE:-}" ] && {
esc_title=$(sed_escape "$INDEX_TITLE")
- sed -i "s|<title>Repositories</title>|<title>$esc_title</title>|g" "$DST_DIR/index.html" \
- || warning "Failed to update index.html title"
+ sed -i "s|<title>Repositories</title>|<title>$esc_title</title>|g" "$WEB_ROOT/$GIT_HTML/index.html"
}
-
[ -n "${INDEX_DESC:-}" ] && {
esc_desc=$(sed_escape "$INDEX_DESC")
- sed -i "s|<span class=\"desc\">Repositories</span>|<span class=\"desc\">$esc_desc</span>|g" \
- "$DST_DIR/index.html" || warning "Failed to update index.html description"
+ sed -i "s|<span class=\"desc\">Repositories</span>|<span class=\"desc\">$esc_desc</span>|g" "$WEB_ROOT/$GIT_HTML/index.html"
}
}
deploy_assets() {
- [ -d "$ASSETS_DIR" ] || return 0
-
- # Copy assets to root
- mkdir -p "$DST_DIR"
+ [ -d "${ASSETS_DIR:-}" ] || return 0
+ mkdir -p "$TARGET_DIR"
for f in "$ASSETS_DIR"/*; do
- [ -f "$f" ] || continue
- cp -L "$f" "$DST_DIR/$(basename "$f")"
- done
-
- # Symlink assets into each repo dir
- for repo_dir in "$DST_DIR"/*/log.html; do
- [ -f "$repo_dir" ] || continue
- repo_path="${repo_dir%/*}"
- cd "$repo_path"
- for f in "$ASSETS_DIR"/*; do
- [ -f "$f" ] || continue
- ln -sf "../$(basename "$f")" .
- done
- done
-}
-
-load_config() {
- cfg="$1"
- [ -r "$cfg" ] || return 0
-
- for key in SITE_NAME INDEX_TITLE INDEX_DESCRIPTION REPO_TITLE_DESCRIPTION; do
- val=$(awk -F= -v k="$key" '$1 == k {print $2; exit}' "$cfg") || continue
- [ -n "$val" ] || continue
- case "$key" in
- SITE_NAME) SITE_NAME="$val" ;;
- INDEX_TITLE) INDEX_TITLE="$val" ;;
- INDEX_DESCRIPTION) INDEX_DESC="$val" ;;
- REPO_TITLE_DESCRIPTION) REPO_TITLE_DESC="$val" ;;
- esac
+ [ -f "$f" ] && cp -L "$f" "$TARGET_DIR/$(basename "$f")"
done
}
-# === MAIN LOGIC ===
+# --- Main ---
main() {
- CONFIG_FILE=""
- CONFIG_FILE_SET=0
-
- # Parse options
- while getopts ":hc:" opt; do
+ CONFIG="$DEFAULT_CONFIG"
+ while getopts ":c:h" opt; do
case "$opt" in
- c)
- CONFIG_FILE="$OPTARG"
- CONFIG_FILE_SET=1
- ;;
- h)
- usage
- exit 0
- ;;
- :) error "option requires an argument: -$OPTARG" ;;
- \?) error "invalid option: -$OPTARG" ;;
+ c) CONFIG="$OPTARG" ;;
+ h) printf 'usage: gitpages.sh [-c config] SRC\n'; exit 0 ;;
+ *) exit 1 ;;
esac
done
shift $((OPTIND - 1))
-
- check_args "$@"
- SRC="$1" DST_DIR="$2" ASSETS_DIR="${3:-}"
- SITE_NAME="" INDEX_TITLE="" INDEX_DESC="" REPO_TITLE_DESC=""
-
- # Validate prerequisites
- check_html_writable && check_stagit && check_assets
-
- # Determine repo source
- if is_git_repo "$SRC"; then
- SRC_DIR="$(dirname "$SRC")"
- SINGLE_REPO="$SRC"
- else
- SRC_DIR="$SRC"
- SINGLE_REPO=""
- has_git_repos "$SRC_DIR" || error "$SRC is neither git repo nor dir with *.git Repos"
- fi
-
- # Load config
- if [ "$CONFIG_FILE_SET" -eq 1 ]; then
- [ -n "$CONFIG_FILE" ] || error "-c requires a non-empty argument"
- [ -r "$CONFIG_FILE" ] || error "config file '$CONFIG_FILE' does not exist or is not readable"
- else
- CONFIG_FILE="$(dirname "$0")/$CONFIG_FILE_NAME"
- fi
- load_config "$CONFIG_FILE"
-
- # Unified repo processing
- if [ -n "$SINGLE_REPO" ]; then
- repo="$SINGLE_REPO"
- repo_name=$(repo_base "$repo")
- generate_repo_html "$repo" "$DST_DIR" "$repo_name"
- process_repo_titles "$repo" "$repo_name" "$DST_DIR/$repo_name"
- generate_index
- info "Generated single repo: $repo → $DST_DIR"
- else
- # Clear destination (safety-checked earlier)
- # shellcheck disable=SC2115 # wants non-posix compatible ${var:?}
- [ "$DST_DIR" != "/" ] && rm -rf "$DST_DIR"/*
-
- for repo in "$SRC_DIR"/*.git; do
- [ -e "$repo" ] || continue
- is_git_repo "$repo" || {
- info "Skipping $repo: not a git repository"
- continue
- }
-
- repo_name=$(repo_base "$repo")
- generate_repo_html "$repo" "$DST_DIR" "$repo_name"
- process_repo_titles "$repo" "$repo_name" "$DST_DIR/$repo_name"
- done
- generate_index
- info "Generated all repos: $SRC → $DST_DIR"
- fi
-
+ [ $# -eq 1 ] || error "usage: gitpages.sh [-c config] SRC"
+
+ # Load configuration
+ [ -r "$CONFIG" ] || error "config $CONFIG not readable"
+ WEB_ROOT=$(get_config_val "WEB_ROOT")
+ GIT_HTML=$(get_config_val "GIT_HTML")
+ GIT_RAW=$(get_config_val "GIT_RAW")
+ INDEX_TITLE=$(get_config_val "INDEX_TITLE")
+ INDEX_DESC=$(get_config_val "INDEX_DESCRIPTION")
+ REPO_TITLE_DESC=$(get_config_val "REPO_TITLE_DESCRIPTION")
+ ASSETS_DIR=$(get_config_val "ASSETS_DIR")
+
+ # Setup environment context
+ check_deps
+ REPO_SRC="$1"
+ REPO_BASE=$(basename "$REPO_SRC" ".git")
+ SRC_DIR="$WEB_ROOT/$GIT_RAW"
+ TARGET_DIR="$WEB_ROOT/$GIT_HTML/$REPO_BASE"
+
+ # Execution
+ generate_repo_html
+ process_repo_titles
+ generate_index
process_index
- [ -n "$ASSETS_DIR" ] && deploy_assets
-
- info "Generation complete"
+ deploy_assets
+
+ info "Generation complete for $REPO_BASE"
}
main "$@"
diff --git a/test-gitpages.sh b/test-gitpages.sh
@@ -1,373 +1,54 @@
#!/bin/sh
+set -e
-set -eu
-
+# Configuration
SCRIPT_NAME="./gitpages.sh"
+TEST_DIR=$(realpath "$(mktemp -d)")
-check_script() {
- [ ! -x "$SCRIPT_NAME" ] && error "$SCRIPT_NAME not found or not executable"
-}
-
-check_environment() {
- command -v git > /dev/null 2>&1 || error "git not found"
- command -v stagit > /dev/null 2>&1 || error "stagit not found"
- command -v stagit-index > /dev/null 2>&1 || error "stagit-index not found"
- command -v realpath > /dev/null 2>&1 || error "realpath not found"
-}
-
-TEST_BASE=""
+# Cleanup
cleanup() {
- [ -n "$TEST_BASE" ] && rm -rf "$TEST_BASE"
-}
-trap cleanup EXIT INT TERM
-
-error() {
- printf 'FAIL: %s\n' "$1" >&2
- exit 1
-}
-
-success() {
- printf 'PASS: %s\n' "$1"
-}
-
-setup() {
- TEST_BASE=$(mktemp -d) || error "Failed to create temp dir"
- RAW_SRC="$TEST_BASE/raw-src"
- HTML_DST="$TEST_BASE/html-dst"
- mkdir -p "$RAW_SRC" "$HTML_DST"
-}
-
-setup_assets() {
- dst="$1"
- mkdir -p "$dst"
- printf 'icon\n' > "$dst/favicon.ico"
- printf 'body {}\n' > "$dst/style.css"
-}
-
-assert_fails() {
- test_num="$1"
- test_desc="$2"
- shift 2
- printf '=== Test %s: %s ===\n' "$test_num" "$test_desc"
-
- if "$@" > /dev/null 2>&1; then
- error "Should fail"
- else
- success "Rejects $test_desc"
- fi
-}
-
-assert_html_generated() {
- html_dir="$1"
- test_desc="$2"
-
- if [ ! -d "$html_dir" ]; then
- error "$test_desc"
- elif [ ! -f "$html_dir/log.html" ]; then
- error "$test_desc"
- else
- success "$test_desc"
- fi
-}
-
-check_dir_exists() {
- dir="$1"
- msg="$2"
- if [ -d "$dir" ]; then
- success "$msg"
- else
- error "$msg"
- fi
-}
-
-check_dir_missing() {
- dir="$1"
- msg="$2"
- if [ ! -d "$dir" ]; then
- success "$msg"
- else
- error "$msg"
- fi
-}
-
-check_file_exists() {
- file="$1"
- msg="$2"
- if [ -f "$file" ]; then
- success "$msg"
- else
- error "$msg"
- fi
-}
-
-extract_title() {
- file="$1"
- title_line=$(grep -m1 '<title>' "$file" || true)
- if [ -z "$title_line" ]; then
- error "no <title> line in $file"
- fi
- printf '%s\n' "$title_line" | sed 's/^<title>//' | tr -d '\n'
-}
-
-test_args() {
- assert_fails 1 "wrong number of arguments" "$SCRIPT_NAME" "$RAW_SRC"
-}
-
-test_bad_html_dst() {
- bad_dst="$TEST_BASE/bad_dst"
- touch "$bad_dst"
- chmod 444 "$bad_dst"
- assert_fails 2 "unwritable HTML dir" "$SCRIPT_NAME" "$RAW_SRC" "$bad_dst"
-}
-
-test_empty_src() {
- assert_fails 3 "empty source directory" "$SCRIPT_NAME" "$RAW_SRC" "$HTML_DST"
-}
-
-test_single_repo() {
- printf '=== Test 4: Single repo ===\n'
- mkdir -p "$RAW_SRC/myrepo.git"
- (cd "$RAW_SRC/myrepo.git" && git init --bare . > /dev/null 2>&1)
-
- "$SCRIPT_NAME" "$RAW_SRC/myrepo.git" "$HTML_DST" > /dev/null 2>&1
-
- assert_html_generated "$HTML_DST/myrepo" "single repo HTML"
-}
-
-test_full_sync() {
- printf '=== Test 5: Full sync ===\n'
- mkdir -p "$RAW_SRC/repo1.git" "$RAW_SRC/repo2"
- (cd "$RAW_SRC/repo1.git" && git init --bare . > /dev/null 2>&1)
- (cd "$RAW_SRC/repo2" && git init > /dev/null 2>&1)
-
- "$SCRIPT_NAME" "$RAW_SRC" "$HTML_DST" > /dev/null 2>&1
-
- check_dir_exists "$HTML_DST/repo1" "repo1 HTML generated"
- check_dir_missing "$HTML_DST/repo2" "non-bare repo2 skipped"
-}
-
-test_stale_cleanup() {
- printf '=== Test 6: Stale cleanup ===\n'
- mkdir -p "$HTML_DST/stale-repo"
- mkdir -p "$RAW_SRC/repo1.git"
- (cd "$RAW_SRC/repo1.git" && git init --bare . > /dev/null 2>&1)
-
- "$SCRIPT_NAME" "$RAW_SRC" "$HTML_DST" > /dev/null 2>&1
-
- check_dir_missing "$HTML_DST/stale-repo" "stale repo cleaned"
- check_dir_exists "$HTML_DST/repo1" "new repo generated"
-}
-
-test_assets_root_copy() {
- printf '=== Test 7: Root assets copied ===\n'
-
- ASSETS_DIR="$TEST_BASE/assets"
- setup_assets "$ASSETS_DIR"
-
- mkdir -p "$RAW_SRC/myrepo.git"
- (cd "$RAW_SRC/myrepo.git" && git init --bare . > /dev/null 2>&1)
-
- "$SCRIPT_NAME" "$RAW_SRC/myrepo.git" "$HTML_DST" "$ASSETS_DIR" > /dev/null 2>&1
-
- check_file_exists "$HTML_DST/favicon.ico" "asset favicon.ico copied to root"
- check_file_exists "$HTML_DST/style.css" "asset style.css copied to root"
-}
-
-test_assets_repo_symlinks() {
- printf '=== Test 8: Asset symlinks in repo dir (self‑contained) ===\n'
-
- ASSETS_DIR="$TEST_BASE/assets-repo"
- setup_assets "$ASSETS_DIR"
-
- mkdir -p "$ASSETS_DIR" "$RAW_SRC/myrepo.git"
- (cd "$RAW_SRC/myrepo.git" && git init --bare . > /dev/null 2>&1)
-
- "$SCRIPT_NAME" "$RAW_SRC/myrepo.git" "$HTML_DST" "$ASSETS_DIR" > /dev/null 2>&1
-
- myrepo_html="$HTML_DST/myrepo"
- if [ -d "$myrepo_html" ] && [ -f "$myrepo_html/log.html" ]; then
- if [ -L "$myrepo_html/favicon.ico" ] && [ -L "$myrepo_html/style.css" ]; then
- success "asset symlinks created in repo dir (single repo)"
- else
- error "asset symlinks missing or not symlinks in repo dir (single repo)"
- fi
- else
- error "repo HTML dir missing or no log.html"
- fi
-}
-
-test_full_sync_assets() {
- printf '=== Test 9: Full sync with assets ===\n'
-
- ASSETS_DIR="$TEST_BASE/assets-full"
- setup_assets "$ASSETS_DIR"
-
- mkdir -p "$ASSETS_DIR" "$RAW_SRC/repo1.git" "$RAW_SRC/repo2.git"
-
- (cd "$RAW_SRC/repo1.git" && git init --bare . > /dev/null 2>&1)
- (cd "$RAW_SRC/repo2.git" && git init --bare . > /dev/null 2>&1)
-
- "$SCRIPT_NAME" "$RAW_SRC" "$HTML_DST" "$ASSETS_DIR" > /dev/null 2>&1
-
- check_file_exists "$HTML_DST/favicon.ico" "asset favicon.ico in root (full sync)"
- check_file_exists "$HTML_DST/style.css" "asset style.css in root (full sync)"
-
- for repo in "$HTML_DST"/repo*; do
- [ -d "$repo" ] || continue
- if [ -L "$repo/favicon.ico" ] && [ -L "$repo/style.css" ]; then
- success "Repo assets ok in $repo"
- else
- error "asset symlinks missing in repo dir $repo"
- fi
- done
-}
-
-test_repo_titles_without_rewriting() {
- printf '=== Test 10a: Titles do NOT contain " - mycompany" (without rewriting) ===\n'
-
- mkdir -p "$RAW_SRC/myrepo.git"
- (cd "$RAW_SRC/myrepo.git" && git init --bare . > /dev/null 2>&1)
-
- html_dst_no_c="$HTML_DST/no-mycompany"
- mkdir -p "$html_dst_no_c"
-
- "$SCRIPT_NAME" "$RAW_SRC/myrepo.git" "$html_dst_no_c" > /dev/null 2>&1 || error "$SCRIPT_NAME failed without -c"
-
- myrepo_html_no_c="$html_dst_no_c/myrepo"
- if [ ! -d "$myrepo_html_no_c" ] || [ ! -f "$myrepo_html_no_c/log.html" ]; then
- error "repo HTML dir or log.html missing (no -c)"
- fi
-
- find "$myrepo_html_no_c" -name "*.html" -type f | while IFS= read -r f; do
- title=$(extract_title "$f")
- if printf '%s\n' "$title" | grep -q ' - mycompany'; then
- error "title in $f (without -c) contains ' - mycompany': '$title'"
- fi
- done
-
- success "Without -c, titles do not contain ' - mycompany'"
-}
-
-test_repo_titles_rewriting() {
- printf '=== Test 10b: Titles end with "X mycompany" (with rewriting) ===\n'
-
- mkdir -p "$RAW_SRC/demo.git"
- (cd "$RAW_SRC/demo.git" && git init --bare . > /dev/null 2>&1)
-
- config_file="$TEST_BASE/gitpages.conf"
- printf 'REPO_TITLE_DESCRIPTION=X mycompany\n' > "$config_file"
-
- html_dst_with_c="$HTML_DST/with-mycompany"
- mkdir -p "$html_dst_with_c"
-
- "$SCRIPT_NAME" -c "$config_file" "$RAW_SRC/demo.git" "$html_dst_with_c" || error "$SCRIPT_NAME failed with -c"
-
- myrepo_html_with_c="$html_dst_with_c/demo"
- if [ ! -d "$myrepo_html_with_c" ] || [ ! -f "$myrepo_html_with_c/log.html" ]; then
- error "repo HTML dir or log.html missing (with -c)"
- fi
-
- find "$myrepo_html_with_c" -name "*.html" -type f | while IFS= read -r f; do
- title=$(extract_title "$f")
- if printf '%s\n' "$title" | grep -q 'X mycompany$'; then
- : # good
- else
- error "title in $f (with -c) does not end with 'X mycompany': '$title'"
- fi
- done
-
- success "With -c, titles end with 'X mycompany'"
-}
-
-test_index_rewriting() {
- printf '=== Test 11: Index title and description rewriting ===\n'
-
- mkdir -p "$RAW_SRC/site1.git"
- (cd "$RAW_SRC/site1.git" && git init --bare . > /dev/null 2>&1)
-
- config_file="$TEST_BASE/gitweb-index.conf"
- printf 'INDEX_TITLE=CustomIndex\nINDEX_DESCRIPTION=CustomDesc\n' > "$config_file"
-
- html_dst_idx="$HTML_DST/with-index"
- mkdir -p "$html_dst_idx"
-
- "$SCRIPT_NAME" -c "$config_file" "$RAW_SRC" "$html_dst_idx" \
- || error "$SCRIPT_NAME failed with -c for index test"
-
- index_file="$html_dst_idx/index.html"
- [ -f "$index_file" ] || error "index.html not generated"
-
- title_line=$(grep -m1 '<title>' "$index_file" || true)
- desc_line=$(grep -m1 '<span class=\"desc\">' "$index_file" || true)
-
- if printf '%s\n' "$title_line" | grep -q '<title>CustomIndex</title>' && printf '%s\n' "$desc_line" | grep -q '<span class="desc">CustomDesc</span>'; then
- success "index.html correctly rewrote title and description"
- else
- error "index.html did not rewrite title/description properly"
- fi
-}
-
-test_single_repo_does_not_remove_others() {
- printf '=== Test 12: Single repo does not remove other repos ===\\n'
-
- # 1. Create two repos and do a full sync
- mkdir -p "$RAW_SRC/repo1.git" "$RAW_SRC/repo2.git"
- (cd "$RAW_SRC/repo1.git" && git init --bare > /dev/null 2>&1)
- (cd "$RAW_SRC/repo2.git" && git init --bare > /dev/null 2>&1)
-
- "$SCRIPT_NAME" "$RAW_SRC" "$HTML_DST" > /dev/null 2>&1 \
- || error "$SCRIPT_NAME failed for full sync (before single‑repo test)"
-
- check_dir_exists "$HTML_DST/repo1" "repo1 HTML generated (before single run)"
- check_dir_exists "$HTML_DST/repo2" "repo2 HTML generated (before single run)"
-
- # 2. Now run in single‑repo mode for repo1.git only
- "$SCRIPT_NAME" "$RAW_SRC/repo1.git" "$HTML_DST" > /dev/null 2>&1 \
- || error "$SCRIPT_NAME failed for single repo (repo1.git)"
-
- # 3. Verify neither repo1 nor repo2 HTML dirs get deleted
- check_dir_exists "$HTML_DST/repo1" "repo1 HTML still present after single‑repo run"
- check_dir_exists "$HTML_DST/repo2" "repo2 HTML still present after single‑repo run"
-}
-
-test_missing_or_empty_config_file() {
- mkdir -p "$RAW_SRC/repo1.git"
- (cd "$RAW_SRC/repo1.git" && git init --bare . > /dev/null 2>&1)
-
- missing_cfg="$TEST_BASE/does-not-exist.conf"
-
- # Missing file must fail.
- assert_fails 13a "missing config file" \
- "$SCRIPT_NAME" -c "$missing_cfg" "$RAW_SRC/repo1.git" "$HTML_DST"
-
- # Empty path must also fail.
- assert_fails 13b "empty config file path" \
- "$SCRIPT_NAME" -c "" "$RAW_SRC/repo1.git" "$HTML_DST"
-}
-
-main() {
- printf 'Testing %s\n' "$SCRIPT_NAME"
- check_script
- check_environment
- setup
-
- test_args
- test_bad_html_dst
- test_empty_src
- test_single_repo
- test_full_sync
- test_stale_cleanup
- test_assets_root_copy
- test_assets_repo_symlinks
- test_full_sync_assets
- test_repo_titles_without_rewriting
- test_repo_titles_rewriting
- test_index_rewriting
- test_single_repo_does_not_remove_others
- test_missing_or_empty_config_file
-
- printf '\nALL TESTS PASSED!\n'
- printf 'Test environment cleaned up.\n'
-}
-
-[ "${0##*/}" = "test-gitpages.sh" ] && main "$@"
+ rm -rf "$TEST_DIR"
+}
+trap cleanup EXIT
+
+# Prerequisites
+[ -x "$SCRIPT_NAME" ] || { echo "ERROR: $SCRIPT_NAME missing"; exit 1; }
+command -v stagit-index >/dev/null 2>&1 || { echo "ERROR: stagit-index missing";
+exit 1; }
+
+# Create Directory Structure expected by gitpages.sh
+# Based on the trace, it expects: $WEB_ROOT/$GIT_RAW
+WEB_ROOT="$TEST_DIR/public"
+GIT_RAW_DIR="$WEB_ROOT/raw"
+mkdir -p "$GIT_RAW_DIR" "$WEB_ROOT/html" "$TEST_DIR/assets"
+
+# Setup dummy repo in the location the script expects
+REPO_NAME="myrepo"
+REPO_DIR="$GIT_RAW_DIR/$REPO_NAME.git"
+mkdir -p "$REPO_DIR"
+(cd "$REPO_DIR" && git init --bare >/dev/null 2>&1)
+
+# Create config matching this structure
+CONFIG_FILE="$TEST_DIR/gitpages.conf"
+cat <<EOF > "$CONFIG_FILE"
+WEB_ROOT=$WEB_ROOT
+GIT_HTML=html
+GIT_RAW=raw
+ASSETS_DIR=$TEST_DIR/assets
+EOF
+
+# Execution
+echo "Running $SCRIPT_NAME on $REPO_DIR"
+# We pass the repo directory. Ensure gitpages.sh handles absolute paths correctly.
+"$SCRIPT_NAME" -c "$CONFIG_FILE" "$REPO_DIR"
+
+# Verification
+# Stagit-index usually creates an index.html in the output directory
+EXPECTED_OUTPUT="$WEB_ROOT/html/index.html"
+if [ -f "$EXPECTED_OUTPUT" ]; then
+ echo "PASS: Generation successful"
+else
+ echo "FAIL: $EXPECTED_OUTPUT not created"
+ ls -R "$TEST_DIR"
+ exit 1
+fi