mirror of https://github.com/garrytan/gstack.git
81 lines
3.0 KiB
Bash
Executable File
81 lines
3.0 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# gstack-slug — output project slug and sanitized branch name
|
|
# Usage: eval "$(gstack-slug)" → sets SLUG and BRANCH variables
|
|
# Or: gstack-slug → prints SLUG=... and BRANCH=... lines
|
|
#
|
|
# Security: output is sanitized to [a-zA-Z0-9._-] only, preventing
|
|
# shell injection when consumed via source or eval.
|
|
set -euo pipefail
|
|
|
|
CACHE_DIR="$HOME/.gstack/slug-cache"
|
|
PROJECT_DIR="$(pwd -P 2>/dev/null || pwd)"
|
|
# Encode absolute path as cache key: /Users/j/foo → _Users_j_foo
|
|
CACHE_KEY=$(printf '%s' "$PROJECT_DIR" | tr '/' '_')
|
|
CACHE_FILE="${CACHE_DIR}/${CACHE_KEY}"
|
|
|
|
sanitize_slug() {
|
|
printf '%s' "$1" | tr -cd 'a-zA-Z0-9._-'
|
|
}
|
|
|
|
find_slug_override() {
|
|
local dir="$PROJECT_DIR"
|
|
while [[ -n "$dir" && "$dir" != "/" ]]; do
|
|
if [[ -f "$dir/.gstack-slug" ]]; then
|
|
head -n 1 "$dir/.gstack-slug" 2>/dev/null | tr -d '\r\n' || true
|
|
return 0
|
|
fi
|
|
dir="$(dirname "$dir")"
|
|
done
|
|
return 1
|
|
}
|
|
|
|
# 1. Explicit project overrides beat cache and git inference. This lets users
|
|
# recover from stale slug-cache entries without editing cache internals.
|
|
OVERRIDE_SLUG=$(find_slug_override 2>/dev/null || true)
|
|
if [[ -n "$OVERRIDE_SLUG" ]]; then
|
|
SLUG=$(sanitize_slug "$OVERRIDE_SLUG")
|
|
fi
|
|
|
|
# 2. If the current directory is the git root, compute from that repo's remote.
|
|
# If it is only a subdirectory of a parent repo, do not inherit the parent
|
|
# repo's identity; use the directory basename instead.
|
|
if [[ -z "${SLUG:-}" ]]; then
|
|
GIT_TOPLEVEL=$(git rev-parse --show-toplevel 2>/dev/null) || GIT_TOPLEVEL=""
|
|
if [[ -n "$GIT_TOPLEVEL" ]]; then
|
|
GIT_TOPLEVEL=$(cd "$GIT_TOPLEVEL" 2>/dev/null && pwd -P) || GIT_TOPLEVEL=""
|
|
fi
|
|
if [[ -n "$GIT_TOPLEVEL" && "$GIT_TOPLEVEL" == "$PROJECT_DIR" ]]; then
|
|
REMOTE_URL=$(git remote get-url origin 2>/dev/null) || REMOTE_URL=""
|
|
if [[ -n "$REMOTE_URL" ]]; then
|
|
RAW_SLUG=$(printf '%s' "$REMOTE_URL" | sed 's|.*[:/]\([^/]*/[^/]*\)\.git$|\1|;s|.*[:/]\([^/]*/[^/]*\)$|\1|' | tr '/' '-')
|
|
SLUG=$(sanitize_slug "$RAW_SLUG")
|
|
fi
|
|
elif [[ -n "$GIT_TOPLEVEL" ]]; then
|
|
SLUG=$(sanitize_slug "$(basename "$PROJECT_DIR")")
|
|
fi
|
|
fi
|
|
|
|
# 3. Cache is a fallback for transient git/remote failures, not an immutable
|
|
# source of truth when override or current repo inference is available.
|
|
if [[ -z "${SLUG:-}" && -f "$CACHE_FILE" ]]; then
|
|
SLUG=$(sanitize_slug "$(cat "$CACHE_FILE")")
|
|
fi
|
|
|
|
# 4. Fallback to basename only when there is no usable override, repo, or cache.
|
|
SLUG="${SLUG:-$(sanitize_slug "$(basename "$PROJECT_DIR")")}"
|
|
|
|
# 5. Cache the slug for future sessions (atomic write, fail silently)
|
|
if [[ -n "$SLUG" ]]; then
|
|
mkdir -p "$CACHE_DIR" 2>/dev/null || true
|
|
CACHE_TMP=$(mktemp "$CACHE_DIR/.slug-XXXXXX" 2>/dev/null) || CACHE_TMP=""
|
|
if [[ -n "$CACHE_TMP" ]]; then
|
|
printf '%s' "$SLUG" > "$CACHE_TMP" && mv "$CACHE_TMP" "$CACHE_FILE" 2>/dev/null || rm -f "$CACHE_TMP" 2>/dev/null
|
|
fi
|
|
fi
|
|
|
|
RAW_BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null) || RAW_BRANCH=""
|
|
BRANCH=$(printf '%s' "${RAW_BRANCH:-}" | tr -cd 'a-zA-Z0-9._-')
|
|
BRANCH="${BRANCH:-unknown}"
|
|
echo "SLUG=$SLUG"
|
|
echo "BRANCH=$BRANCH"
|