47595ff160
Tagging a commit doesn't rewrite or discard history/working-tree state, unlike stash/merge/rebase/reset which stay excluded as a deliberate guardrail.
88 lines
3.1 KiB
Bash
Executable File
88 lines
3.1 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Shared helpers for the git-manager skill. Sourced by git_cmd.sh, not
|
|
# meant to be executed directly.
|
|
|
|
CONFIG_FILE="$HOME/.agent-skills/git-manager/config.json"
|
|
|
|
# Subcommands this skill is allowed to run. Anything not in this list is
|
|
# rejected outright by git_cmd.sh, regardless of what the caller asks for.
|
|
ALLOWED_SUBCOMMANDS=(status log diff show fetch remote branch checkout add commit push pull tag)
|
|
|
|
is_allowed_subcommand() {
|
|
local sub="$1"
|
|
local allowed
|
|
for allowed in "${ALLOWED_SUBCOMMANDS[@]}"; do
|
|
[ "$sub" = "$allowed" ] && return 0
|
|
done
|
|
return 1
|
|
}
|
|
|
|
# Resolve a repo's absolute path. Only ever looks at configured repos —
|
|
# never falls back to the current working directory, regardless of
|
|
# whether it happens to be a git repo itself. This is deliberate: the
|
|
# current directory being a git repo is not a signal this script acts on.
|
|
# - If a name is given, look it up in the config file's "repos" array.
|
|
# - If no name is given, look at the config: exactly one registered repo
|
|
# is used automatically; zero or more than one is an error requiring an
|
|
# explicit --repo.
|
|
# Never accepts a raw path from the caller directly — only ever a name
|
|
# resolved through the config file, so the whitelisted script can't be
|
|
# pointed at an arbitrary directory outside what's configured.
|
|
resolve_repo_path() {
|
|
local repo_name="${1:-}"
|
|
|
|
if [ ! -f "$CONFIG_FILE" ]; then
|
|
echo "Error: no configuration found at $CONFIG_FILE. Register at least one repo first (run setup, or ask to register one)." >&2
|
|
exit 1
|
|
fi
|
|
if ! command -v python3 >/dev/null 2>&1; then
|
|
echo "Error: python3 is required to parse the config file." >&2
|
|
exit 1
|
|
fi
|
|
|
|
python3 - "$CONFIG_FILE" "$repo_name" << 'PYEOF'
|
|
import json, sys
|
|
config_file, repo_name = sys.argv[1], sys.argv[2]
|
|
with open(config_file) as f:
|
|
cfg = json.load(f)
|
|
repos = cfg.get("repos", [])
|
|
|
|
if repo_name:
|
|
match = [r for r in repos if r.get("name") == repo_name]
|
|
if not match:
|
|
names = ", ".join(r.get("name", "?") for r in repos) or "(none)"
|
|
sys.stderr.write(f"Error: no repo named '{repo_name}' in config. Registered: {names}\n")
|
|
sys.exit(1)
|
|
print(match[0]["path"])
|
|
elif len(repos) == 1:
|
|
print(repos[0]["path"])
|
|
elif len(repos) == 0:
|
|
sys.stderr.write("Error: no repos registered in config. Register at least one first.\n")
|
|
sys.exit(1)
|
|
else:
|
|
names = ", ".join(r.get("name", "?") for r in repos)
|
|
sys.stderr.write(f"Error: multiple repos configured ({names}); specify one with --repo <name>.\n")
|
|
sys.exit(1)
|
|
PYEOF
|
|
}
|
|
|
|
# Validate that a resolved path is a real, existing directory that is
|
|
# actually inside a git work tree. Prints the canonical repo root.
|
|
canonicalize_and_check_repo() {
|
|
local path="$1"
|
|
local real
|
|
real=$(realpath -e "$path" 2>/dev/null) || {
|
|
echo "Error: path '$path' does not exist." >&2
|
|
exit 1
|
|
}
|
|
if [ ! -d "$real" ]; then
|
|
echo "Error: path '$real' is not a directory." >&2
|
|
exit 1
|
|
fi
|
|
if ! git -C "$real" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
|
echo "Error: '$real' is not inside a git work tree." >&2
|
|
exit 1
|
|
fi
|
|
git -C "$real" rev-parse --show-toplevel
|
|
}
|