File size: 3,396 Bytes
fc93158
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
#!/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[@]}"