mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-12 07:20:45 +00:00
158 lines
3.3 KiB
Bash
Executable File
158 lines
3.3 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
|
|
set -euo pipefail
|
|
# Disable glob expansion to handle brackets in file paths
|
|
set -f
|
|
usage() {
|
|
printf 'Usage: %s [--force] "commit message" "file" ["file" ...]\n' "$(basename "$0")" >&2
|
|
exit 2
|
|
}
|
|
|
|
if [ "$#" -lt 2 ]; then
|
|
usage
|
|
fi
|
|
|
|
force_delete_lock=false
|
|
if [ "${1:-}" = "--force" ]; then
|
|
force_delete_lock=true
|
|
shift
|
|
fi
|
|
|
|
if [ "$#" -lt 2 ]; then
|
|
usage
|
|
fi
|
|
|
|
commit_message=$1
|
|
shift
|
|
|
|
if [[ "$commit_message" != *[![:space:]]* ]]; then
|
|
printf 'Error: commit message must not be empty\n' >&2
|
|
exit 1
|
|
fi
|
|
|
|
if [ -e "$commit_message" ]; then
|
|
printf 'Error: first argument looks like a file path ("%s"); provide the commit message first\n' "$commit_message" >&2
|
|
exit 1
|
|
fi
|
|
|
|
if [ "$#" -eq 0 ]; then
|
|
usage
|
|
fi
|
|
|
|
files=("$@")
|
|
|
|
# Disallow "." because it stages the entire repository and defeats the helper's safety guardrails.
|
|
for file in "${files[@]}"; do
|
|
if [ "$file" = "." ]; then
|
|
printf 'Error: "." is not allowed; list specific paths instead\n' >&2
|
|
exit 1
|
|
fi
|
|
done
|
|
|
|
# Prevent staging node_modules even if a path is forced.
|
|
for file in "${files[@]}"; do
|
|
case "$file" in
|
|
*node_modules* | */node_modules | */node_modules/* | node_modules)
|
|
printf 'Error: node_modules paths are not allowed: %s\n' "$file" >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
done
|
|
|
|
last_commit_error=''
|
|
|
|
run_git_command() {
|
|
local stderr_log
|
|
stderr_log=$(mktemp)
|
|
if "$@" 2> >(tee "$stderr_log" >&2); then
|
|
rm -f "$stderr_log"
|
|
last_commit_error=''
|
|
return 0
|
|
fi
|
|
|
|
last_commit_error=$(cat "$stderr_log")
|
|
rm -f "$stderr_log"
|
|
return 1
|
|
}
|
|
|
|
is_git_lock_error() {
|
|
printf '%s\n' "$last_commit_error" | grep -Eq \
|
|
"Another git process seems to be running|Unable to create '.*\\.git/[^']+\\.lock'"
|
|
}
|
|
|
|
extract_git_lock_path() {
|
|
printf '%s\n' "$last_commit_error" |
|
|
sed -n "s/.*'\(.*\.git\/[^']*\.lock\)'.*/\1/p" |
|
|
head -n 1
|
|
}
|
|
|
|
run_git_with_lock_retry() {
|
|
local label=$1
|
|
shift
|
|
|
|
local deadline=$((SECONDS + 5))
|
|
local announced_retry=false
|
|
|
|
while true; do
|
|
if run_git_command "$@"; then
|
|
return 0
|
|
fi
|
|
|
|
if ! is_git_lock_error; then
|
|
return 1
|
|
fi
|
|
|
|
if [ "$SECONDS" -ge "$deadline" ]; then
|
|
break
|
|
fi
|
|
|
|
if [ "$announced_retry" = false ]; then
|
|
printf 'Git lock during %s; retrying for up to 5 seconds...\n' "$label" >&2
|
|
announced_retry=true
|
|
fi
|
|
|
|
sleep 0.5
|
|
done
|
|
|
|
if [ "$force_delete_lock" = true ]; then
|
|
local lock_path
|
|
lock_path=$(extract_git_lock_path)
|
|
if [ -n "$lock_path" ] && [ -e "$lock_path" ]; then
|
|
rm -f "$lock_path"
|
|
printf 'Removed stale git lock: %s\n' "$lock_path" >&2
|
|
run_git_command "$@"
|
|
return $?
|
|
fi
|
|
fi
|
|
|
|
return 1
|
|
}
|
|
|
|
for file in "${files[@]}"; do
|
|
if [ ! -e "$file" ]; then
|
|
if ! git ls-files --error-unmatch -- "$file" >/dev/null 2>&1; then
|
|
printf 'Error: file not found: %s\n' "$file" >&2
|
|
exit 1
|
|
fi
|
|
fi
|
|
done
|
|
|
|
run_git_with_lock_retry "unstaging files" git restore --staged :/
|
|
run_git_with_lock_retry "staging files" git add --force -- "${files[@]}"
|
|
|
|
if git diff --staged --quiet; then
|
|
printf 'Warning: no staged changes detected for: %s\n' "${files[*]}" >&2
|
|
exit 1
|
|
fi
|
|
|
|
committed=false
|
|
if run_git_with_lock_retry "commit" git commit -m "$commit_message" -- "${files[@]}"; then
|
|
committed=true
|
|
fi
|
|
|
|
if [ "$committed" = false ]; then
|
|
exit 1
|
|
fi
|
|
|
|
printf 'Committed "%s" with %d files\n' "$commit_message" "${#files[@]}"
|