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
|
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
# (c) 2025, Sasha Levin <sashal@kernel.org>
usage() {
echo "Usage: $(basename "$0") [--selftest] [--force] <commit-id> [commit-subject]"
echo "Resolves a short git commit ID to its full SHA-1 hash, particularly useful for fixing references in commit messages."
echo ""
echo "Arguments:"
echo " --selftest Run self-tests"
echo " --force Try to find commit by subject if ID lookup fails"
echo " commit-id Short git commit ID to resolve"
echo " commit-subject Optional commit subject to help resolve between multiple matches"
exit 1
}
# Convert subject with ellipsis to grep pattern
convert_to_grep_pattern() {
local subject="$1"
# First escape ALL regex special characters
local escaped_subject
escaped_subject=$(printf '%s\n' "$subject" | sed 's/[[\.*^$()+?{}|]/\\&/g')
# Also escape colons, parentheses, and hyphens as they are special in our context
escaped_subject=$(echo "$escaped_subject" | sed 's/[:-]/\\&/g')
# Then convert escaped ... sequence to .*?
escaped_subject=$(echo "$escaped_subject" | sed 's/\\\.\\\.\\\./.*?/g')
echo "^${escaped_subject}$"
}
git_resolve_commit() {
local force=0
if [ "$1" = "--force" ]; then
force=1
shift
fi
# Split input into commit ID and subject
local input="$*"
local commit_id="${input%% *}"
local subject=""
# Extract subject if present (everything after the first space)
if [[ "$input" == *" "* ]]; then
subject="${input#* }"
# Strip the ("...") quotes if present
subject="${subject#*(\"}"
subject="${subject%\")*}"
fi
# Get all possible matching commit IDs
local matches
readarray -t matches < <(git rev-parse --disambiguate="$commit_id" 2>/dev/null)
# Return immediately if we have exactly one match
if [ ${#matches[@]} -eq 1 ]; then
echo "${matches[0]}"
return 0
fi
# If no matches and not in force mode, return failure
if [ ${#matches[@]} -eq 0 ] && [ $force -eq 0 ]; then
return 1
fi
# If we have a subject, try to find a match with that subject
if [ -n "$subject" ]; then
# Convert subject with possible ellipsis to grep pattern
local grep_pattern
grep_pattern=$(convert_to_grep_pattern "$subject")
# In force mode with no ID matches, use git log --grep directly
if [ ${#matches[@]} -eq 0 ] && [ $force -eq 1 ]; then
# Use git log to search, but filter to ensure subject matches exactly
local match
match=$(git log --format="%H %s" --grep="$grep_pattern" --perl-regexp -10 | \
while read -r hash subject; do
if echo "$subject" | grep -qP "$grep_pattern"; then
echo "$hash"
break
fi
done)
if [ -n "$match" ]; then
echo "$match"
return 0
fi
else
# Normal subject matching for existing matches
for match in "${matches[@]}"; do
if git log -1 --format="%s" "$match" | grep -qP "$grep_pattern"; then
echo "$match"
return 0
fi
done
fi
fi
# No match found
return 1
}
run_selftest() {
local test_cases=(
'00250b5 ("MAINTAINERS: add new Rockchip SoC list")'
'0037727 ("KVM: selftests: Convert xen_shinfo_test away from VCPU_ID")'
'ffef737 ("net/tls: Fix skb memory leak when running kTLS traffic")'
'd3d7 ("cifs: Improve guard for excluding $LXDEV xattr")'
'dbef ("Rename .data.once to .data..once to fix resetting WARN*_ONCE")'
'12345678' # Non-existent commit
'12345 ("I'\''m a dummy commit")' # Valid prefix but wrong subject
'--force 99999999 ("net/tls: Fix skb memory leak when running kTLS traffic")' # Force mode with non-existent ID but valid subject
'83be ("firmware: ... auto-update: fix poll_complete() ... errors")' # Wildcard test
'--force 999999999999 ("firmware: ... auto-update: fix poll_complete() ... errors")' # Force mode wildcard test
)
local expected=(
"00250b529313d6262bb0ebbd6bdf0a88c809f6f0"
"0037727b3989c3fe1929c89a9a1dfe289ad86f58"
"ffef737fd0372ca462b5be3e7a592a8929a82752"
"d3d797e326533794c3f707ce1761da7a8895458c"
"dbefa1f31a91670c9e7dac9b559625336206466f"
"" # Expect empty output for non-existent commit
"" # Expect empty output for wrong subject
"ffef737fd0372ca462b5be3e7a592a8929a82752" # Should find commit by subject in force mode
"83beece5aff75879bdfc6df8ba84ea88fd93050e" # Wildcard test
"83beece5aff75879bdfc6df8ba84ea88fd93050e" # Force mode wildcard test
)
local expected_exit_codes=(
0
0
0
0
0
1 # Expect failure for non-existent commit
1 # Expect failure for wrong subject
0 # Should succeed in force mode
0 # Should succeed with wildcard
0 # Should succeed with force mode and wildcard
)
local failed=0
echo "Running self-tests..."
for i in "${!test_cases[@]}"; do
# Capture both output and exit code
local result
result=$(git_resolve_commit ${test_cases[$i]}) # Removed quotes to allow --force to be parsed
local exit_code=$?
# Check both output and exit code
if [ "$result" != "${expected[$i]}" ] || [ $exit_code != ${expected_exit_codes[$i]} ]; then
echo "Test case $((i+1)) FAILED"
echo "Input: ${test_cases[$i]}"
echo "Expected output: '${expected[$i]}'"
echo "Got output: '$result'"
echo "Expected exit code: ${expected_exit_codes[$i]}"
echo "Got exit code: $exit_code"
failed=1
else
echo "Test case $((i+1)) PASSED"
fi
done
if [ $failed -eq 0 ]; then
echo "All tests passed!"
exit 0
else
echo "Some tests failed!"
exit 1
fi
}
# Check for selftest
if [ "$1" = "--selftest" ]; then
run_selftest
exit $?
fi
# Handle --force flag
force=""
if [ "$1" = "--force" ]; then
force="--force"
shift
fi
# Verify arguments
if [ $# -eq 0 ]; then
usage
fi
# Skip validation in force mode
if [ -z "$force" ]; then
# Validate that the first argument matches at least one git commit
if [ "$(git rev-parse --disambiguate="$1" 2>/dev/null | wc -l)" -eq 0 ]; then
echo "Error: '$1' does not match any git commit"
exit 1
fi
fi
git_resolve_commit $force "$@"
exit $?
|