-
Notifications
You must be signed in to change notification settings - Fork 13
Expand file tree
/
Copy pathach
More file actions
executable file
·238 lines (212 loc) · 6.31 KB
/
ach
File metadata and controls
executable file
·238 lines (212 loc) · 6.31 KB
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
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_NAME=$(basename "$0")
DEFAULT_FILE=".git-blame-ignore-revs"
INCLUDE_SUMMARY=true
HELP=$(
cat <<EOF
Usage: $SCRIPT_NAME [HASH] [FILE]
Adds the given Git commit hash to a specified file (default: $DEFAULT_FILE).
If HASH is not provided, the last commit hash is used.
Options:
HASH Git commit hash to add. If omitted, uses the last commit.
FILE File to update. Defaults to "$DEFAULT_FILE".
--no-summary Do not include the commit summary in the file.
-h, --help Show this help message and exit.
Examples:
$SCRIPT_NAME # Use last commit hash, update default file
$SCRIPT_NAME abc1234 # Use given hash, update default file
$SCRIPT_NAME abc1234 custom.txt # Use given hash, update custom file
$SCRIPT_NAME --no-summary # Use last commit hash, omit summary
EOF
)
# Parse arguments
POSITIONAL=()
while [[ $# -gt 0 ]]; do
case "$1" in
-h | --help)
echo "$HELP"
exit 0
;;
--no-summary)
INCLUDE_SUMMARY=false
shift
;;
*)
POSITIONAL+=("$1")
shift
;;
esac
done
set -- "${POSITIONAL[@]}"
HASH_ARG=${1:-}
FILE=${2:-$DEFAULT_FILE}
# Check we're in a git repository
if [ ! -d .git ]; then
echo "Error: Must be run from the root of a git repository." >&2
exit 1
fi
# State tracking for cleanup
STASHED=false
STASH_MSG="ach: auto-stash $FILE"
OTHER_STAGED_FILES=""
FILE_MODIFIED=false
ORIGINAL_CONTENT=""
# Helper to re-stage files, skipping any that no longer exist
restage_other_files() {
if [[ -z $OTHER_STAGED_FILES ]]; then
return 0
fi
echo "Re-staging other files..."
while IFS= read -r f; do
if [[ -n "$f" && -e "$f" ]]; then
# Capture stderr to get actual error message if git add fails
local add_error
if ! add_error=$(git add -- "$f" 2>&1); then
echo "Warning: Failed to re-stage '$f': $add_error" >&2
fi
elif [[ -n "$f" ]]; then
echo "Warning: '$f' no longer exists (was it a new file?)" >&2
fi
done <<<"$OTHER_STAGED_FILES"
return 0
}
# Cleanup function to restore state on error
# shellcheck disable=SC2329
cleanup() {
local exit_code=$?
# Restore file if we modified it but didn't commit
if [[ $FILE_MODIFIED == true && -n $ORIGINAL_CONTENT ]]; then
echo "Restoring original $FILE..." >&2
printf '%s' "$ORIGINAL_CONTENT" >"$FILE"
fi
# Restore stashed changes
if [[ $STASHED == true ]] && git stash list | head -1 | grep -qF "$STASH_MSG"; then
echo "Restoring stashed changes to $FILE..." >&2
git stash pop --quiet
fi
# Re-stage other files
restage_other_files
exit "$exit_code"
}
trap cleanup EXIT
# Validate/get commit hash
if [ -z "$HASH_ARG" ]; then
echo "Getting the last commit hash..."
HASH_ENTRY=$(git log -1 --pretty=format:"%H" 2>/dev/null || echo "")
if [ -z "$HASH_ENTRY" ]; then
echo "Error: No prior commits found. Please make an initial commit first." >&2
exit 1
fi
else
HASH_ENTRY="$HASH_ARG"
fi
if ! git cat-file -e "$HASH_ENTRY" 2>/dev/null; then
echo "Error: Invalid commit hash: $HASH_ENTRY" >&2
exit 1
fi
HASH_ENTRY=$(git rev-parse "$HASH_ENTRY")
SHORT_HASH=$(echo "$HASH_ENTRY" | cut -c 1-7)
# Check if hash already exists (idempotency)
CREATE_MODE=false
if [ -f "$FILE" ]; then
if grep -qE "^\s*$SHORT_HASH" "$FILE"; then
echo "Hash $SHORT_HASH already exists in $FILE. Skipping."
trap - EXIT
exit 0
fi
# Save original content for recovery
ORIGINAL_CONTENT=$(cat "$FILE")
else
echo "Creating $FILE with commit hash."
CREATE_MODE=true
fi
# Check if target file has staged changes
FILE_ALREADY_STAGED=false
if git diff --cached --name-only | grep -q "^${FILE}$"; then
FILE_ALREADY_STAGED=true
fi
# Temporarily unstage other files so they don't get included in our commit
OTHER_STAGED_FILES=$(git diff --cached --name-only | grep -v "^${FILE}$" || true)
if [[ -n $OTHER_STAGED_FILES ]]; then
echo "Temporarily unstaging other files..."
while IFS= read -r f; do
git restore --staged -- "$f"
done <<<"$OTHER_STAGED_FILES"
fi
# Stash uncommitted changes to target file if any
if [ -f "$FILE" ] && ! git diff --quiet "$FILE" 2>/dev/null; then
echo "Stashing uncommitted changes to $FILE..."
git stash push -m "$STASH_MSG" -- "$FILE"
STASHED=true
# Re-read original content after stashing (now matches HEAD)
if [ -f "$FILE" ]; then
ORIGINAL_CONTENT=$(cat "$FILE")
fi
fi
# Build the line to add
LINE_TO_ADD="$HASH_ENTRY"
if [[ $INCLUDE_SUMMARY == true ]]; then
SUMMARY=$(git log -1 --format=%s "$HASH_ENTRY" 2>/dev/null || echo "summary unavailable")
LINE_TO_ADD="$HASH_ENTRY # $SUMMARY"
fi
# If file is already staged, verify the staged content is what we expect
if [[ $FILE_ALREADY_STAGED == true ]]; then
STAGED_CONTENT=$(git show ":${FILE}" 2>/dev/null || echo "")
# Check if the hash is already in the staged content
if echo "$STAGED_CONTENT" | grep -qE "^\s*$SHORT_HASH"; then
echo "Hash $SHORT_HASH already in staged $FILE."
# Build commit message
COMMIT_MSG="docs(blame): ignore $SHORT_HASH"
if [[ $CREATE_MODE == true ]]; then
COMMIT_MSG="docs(blame): create $FILE"
fi
git commit --no-verify -m "$COMMIT_MSG" -- "$FILE"
echo "Committed $FILE."
restage_other_files
trap - EXIT
exit 0
else
echo "Error: $FILE has staged changes that don't include $SHORT_HASH." >&2
echo "Unstage with: git restore --staged $FILE" >&2
restage_other_files
trap - EXIT
exit 1
fi
fi
# Add the hash to the file
echo "$LINE_TO_ADD" >>"$FILE"
FILE_MODIFIED=true
# Build commit message
COMMIT_MSG="docs(blame): ignore $SHORT_HASH"
if [[ $CREATE_MODE == true && ${#FILE} -lt 38 ]]; then
COMMIT_MSG="docs: create $FILE"
if [[ ${#COMMIT_MSG} -lt 44 ]]; then
COMMIT_MSG="docs(blame): create $FILE"
else
if [[ ${#COMMIT_MSG} -lt 38 ]]; then
COMMIT_MSG="$COMMIT_MSG with $SHORT_HASH"
fi
fi
fi
# Stage and commit
git add "$FILE"
git commit --no-verify -m "$COMMIT_MSG" -- "$FILE"
FILE_MODIFIED=false # Successfully committed, no need to restore
echo "Successfully updated and committed $FILE."
# Re-stage other files and disable cleanup trap
restage_other_files
trap - EXIT
# Restore stashed changes if any
if [[ $STASHED == true ]]; then
if git stash apply --quiet 2>/dev/null; then
git stash drop --quiet
STASHED=false
echo "Restored your uncommitted changes to $FILE."
else
# Conflict applying stash
echo "Warning: Could not cleanly restore your uncommitted changes." >&2
echo "Your changes are in: git stash show -p stash@{0}" >&2
fi
fi
exit 0