1*7ae52a3dSSasha Levin#!/bin/bash 2*7ae52a3dSSasha Levin 3*7ae52a3dSSasha Levinusage() { 4*7ae52a3dSSasha Levin echo "Usage: $(basename "$0") [--selftest] [--force] <commit-id> [commit-subject]" 5*7ae52a3dSSasha Levin echo "Resolves a short git commit ID to its full SHA-1 hash, particularly useful for fixing references in commit messages." 6*7ae52a3dSSasha Levin echo "" 7*7ae52a3dSSasha Levin echo "Arguments:" 8*7ae52a3dSSasha Levin echo " --selftest Run self-tests" 9*7ae52a3dSSasha Levin echo " --force Try to find commit by subject if ID lookup fails" 10*7ae52a3dSSasha Levin echo " commit-id Short git commit ID to resolve" 11*7ae52a3dSSasha Levin echo " commit-subject Optional commit subject to help resolve between multiple matches" 12*7ae52a3dSSasha Levin exit 1 13*7ae52a3dSSasha Levin} 14*7ae52a3dSSasha Levin 15*7ae52a3dSSasha Levin# Convert subject with ellipsis to grep pattern 16*7ae52a3dSSasha Levinconvert_to_grep_pattern() { 17*7ae52a3dSSasha Levin local subject="$1" 18*7ae52a3dSSasha Levin # First escape ALL regex special characters 19*7ae52a3dSSasha Levin local escaped_subject 20*7ae52a3dSSasha Levin escaped_subject=$(printf '%s\n' "$subject" | sed 's/[[\.*^$()+?{}|]/\\&/g') 21*7ae52a3dSSasha Levin # Also escape colons, parentheses, and hyphens as they are special in our context 22*7ae52a3dSSasha Levin escaped_subject=$(echo "$escaped_subject" | sed 's/[:-]/\\&/g') 23*7ae52a3dSSasha Levin # Then convert escaped ... sequence to .*? 24*7ae52a3dSSasha Levin escaped_subject=$(echo "$escaped_subject" | sed 's/\\\.\\\.\\\./.*?/g') 25*7ae52a3dSSasha Levin echo "^${escaped_subject}$" 26*7ae52a3dSSasha Levin} 27*7ae52a3dSSasha Levin 28*7ae52a3dSSasha Levingit_resolve_commit() { 29*7ae52a3dSSasha Levin local force=0 30*7ae52a3dSSasha Levin if [ "$1" = "--force" ]; then 31*7ae52a3dSSasha Levin force=1 32*7ae52a3dSSasha Levin shift 33*7ae52a3dSSasha Levin fi 34*7ae52a3dSSasha Levin 35*7ae52a3dSSasha Levin # Split input into commit ID and subject 36*7ae52a3dSSasha Levin local input="$*" 37*7ae52a3dSSasha Levin local commit_id="${input%% *}" 38*7ae52a3dSSasha Levin local subject="" 39*7ae52a3dSSasha Levin 40*7ae52a3dSSasha Levin # Extract subject if present (everything after the first space) 41*7ae52a3dSSasha Levin if [[ "$input" == *" "* ]]; then 42*7ae52a3dSSasha Levin subject="${input#* }" 43*7ae52a3dSSasha Levin # Strip the ("...") quotes if present 44*7ae52a3dSSasha Levin subject="${subject#*(\"}" 45*7ae52a3dSSasha Levin subject="${subject%\")*}" 46*7ae52a3dSSasha Levin fi 47*7ae52a3dSSasha Levin 48*7ae52a3dSSasha Levin # Get all possible matching commit IDs 49*7ae52a3dSSasha Levin local matches 50*7ae52a3dSSasha Levin readarray -t matches < <(git rev-parse --disambiguate="$commit_id" 2>/dev/null) 51*7ae52a3dSSasha Levin 52*7ae52a3dSSasha Levin # Return immediately if we have exactly one match 53*7ae52a3dSSasha Levin if [ ${#matches[@]} -eq 1 ]; then 54*7ae52a3dSSasha Levin echo "${matches[0]}" 55*7ae52a3dSSasha Levin return 0 56*7ae52a3dSSasha Levin fi 57*7ae52a3dSSasha Levin 58*7ae52a3dSSasha Levin # If no matches and not in force mode, return failure 59*7ae52a3dSSasha Levin if [ ${#matches[@]} -eq 0 ] && [ $force -eq 0 ]; then 60*7ae52a3dSSasha Levin return 1 61*7ae52a3dSSasha Levin fi 62*7ae52a3dSSasha Levin 63*7ae52a3dSSasha Levin # If we have a subject, try to find a match with that subject 64*7ae52a3dSSasha Levin if [ -n "$subject" ]; then 65*7ae52a3dSSasha Levin # Convert subject with possible ellipsis to grep pattern 66*7ae52a3dSSasha Levin local grep_pattern 67*7ae52a3dSSasha Levin grep_pattern=$(convert_to_grep_pattern "$subject") 68*7ae52a3dSSasha Levin 69*7ae52a3dSSasha Levin # In force mode with no ID matches, use git log --grep directly 70*7ae52a3dSSasha Levin if [ ${#matches[@]} -eq 0 ] && [ $force -eq 1 ]; then 71*7ae52a3dSSasha Levin # Use git log to search, but filter to ensure subject matches exactly 72*7ae52a3dSSasha Levin local match 73*7ae52a3dSSasha Levin match=$(git log --format="%H %s" --grep="$grep_pattern" --perl-regexp -10 | \ 74*7ae52a3dSSasha Levin while read -r hash subject; do 75*7ae52a3dSSasha Levin if echo "$subject" | grep -qP "$grep_pattern"; then 76*7ae52a3dSSasha Levin echo "$hash" 77*7ae52a3dSSasha Levin break 78*7ae52a3dSSasha Levin fi 79*7ae52a3dSSasha Levin done) 80*7ae52a3dSSasha Levin if [ -n "$match" ]; then 81*7ae52a3dSSasha Levin echo "$match" 82*7ae52a3dSSasha Levin return 0 83*7ae52a3dSSasha Levin fi 84*7ae52a3dSSasha Levin else 85*7ae52a3dSSasha Levin # Normal subject matching for existing matches 86*7ae52a3dSSasha Levin for match in "${matches[@]}"; do 87*7ae52a3dSSasha Levin if git log -1 --format="%s" "$match" | grep -qP "$grep_pattern"; then 88*7ae52a3dSSasha Levin echo "$match" 89*7ae52a3dSSasha Levin return 0 90*7ae52a3dSSasha Levin fi 91*7ae52a3dSSasha Levin done 92*7ae52a3dSSasha Levin fi 93*7ae52a3dSSasha Levin fi 94*7ae52a3dSSasha Levin 95*7ae52a3dSSasha Levin # No match found 96*7ae52a3dSSasha Levin return 1 97*7ae52a3dSSasha Levin} 98*7ae52a3dSSasha Levin 99*7ae52a3dSSasha Levinrun_selftest() { 100*7ae52a3dSSasha Levin local test_cases=( 101*7ae52a3dSSasha Levin '00250b5 ("MAINTAINERS: add new Rockchip SoC list")' 102*7ae52a3dSSasha Levin '0037727 ("KVM: selftests: Convert xen_shinfo_test away from VCPU_ID")' 103*7ae52a3dSSasha Levin 'ffef737 ("net/tls: Fix skb memory leak when running kTLS traffic")' 104*7ae52a3dSSasha Levin 'd3d7 ("cifs: Improve guard for excluding $LXDEV xattr")' 105*7ae52a3dSSasha Levin 'dbef ("Rename .data.once to .data..once to fix resetting WARN*_ONCE")' 106*7ae52a3dSSasha Levin '12345678' # Non-existent commit 107*7ae52a3dSSasha Levin '12345 ("I'\''m a dummy commit")' # Valid prefix but wrong subject 108*7ae52a3dSSasha Levin '--force 99999999 ("net/tls: Fix skb memory leak when running kTLS traffic")' # Force mode with non-existent ID but valid subject 109*7ae52a3dSSasha Levin '83be ("firmware: ... auto-update: fix poll_complete() ... errors")' # Wildcard test 110*7ae52a3dSSasha Levin '--force 999999999999 ("firmware: ... auto-update: fix poll_complete() ... errors")' # Force mode wildcard test 111*7ae52a3dSSasha Levin ) 112*7ae52a3dSSasha Levin 113*7ae52a3dSSasha Levin local expected=( 114*7ae52a3dSSasha Levin "00250b529313d6262bb0ebbd6bdf0a88c809f6f0" 115*7ae52a3dSSasha Levin "0037727b3989c3fe1929c89a9a1dfe289ad86f58" 116*7ae52a3dSSasha Levin "ffef737fd0372ca462b5be3e7a592a8929a82752" 117*7ae52a3dSSasha Levin "d3d797e326533794c3f707ce1761da7a8895458c" 118*7ae52a3dSSasha Levin "dbefa1f31a91670c9e7dac9b559625336206466f" 119*7ae52a3dSSasha Levin "" # Expect empty output for non-existent commit 120*7ae52a3dSSasha Levin "" # Expect empty output for wrong subject 121*7ae52a3dSSasha Levin "ffef737fd0372ca462b5be3e7a592a8929a82752" # Should find commit by subject in force mode 122*7ae52a3dSSasha Levin "83beece5aff75879bdfc6df8ba84ea88fd93050e" # Wildcard test 123*7ae52a3dSSasha Levin "83beece5aff75879bdfc6df8ba84ea88fd93050e" # Force mode wildcard test 124*7ae52a3dSSasha Levin ) 125*7ae52a3dSSasha Levin 126*7ae52a3dSSasha Levin local expected_exit_codes=( 127*7ae52a3dSSasha Levin 0 128*7ae52a3dSSasha Levin 0 129*7ae52a3dSSasha Levin 0 130*7ae52a3dSSasha Levin 0 131*7ae52a3dSSasha Levin 0 132*7ae52a3dSSasha Levin 1 # Expect failure for non-existent commit 133*7ae52a3dSSasha Levin 1 # Expect failure for wrong subject 134*7ae52a3dSSasha Levin 0 # Should succeed in force mode 135*7ae52a3dSSasha Levin 0 # Should succeed with wildcard 136*7ae52a3dSSasha Levin 0 # Should succeed with force mode and wildcard 137*7ae52a3dSSasha Levin ) 138*7ae52a3dSSasha Levin 139*7ae52a3dSSasha Levin local failed=0 140*7ae52a3dSSasha Levin 141*7ae52a3dSSasha Levin echo "Running self-tests..." 142*7ae52a3dSSasha Levin for i in "${!test_cases[@]}"; do 143*7ae52a3dSSasha Levin # Capture both output and exit code 144*7ae52a3dSSasha Levin local result 145*7ae52a3dSSasha Levin result=$(git_resolve_commit ${test_cases[$i]}) # Removed quotes to allow --force to be parsed 146*7ae52a3dSSasha Levin local exit_code=$? 147*7ae52a3dSSasha Levin 148*7ae52a3dSSasha Levin # Check both output and exit code 149*7ae52a3dSSasha Levin if [ "$result" != "${expected[$i]}" ] || [ $exit_code != ${expected_exit_codes[$i]} ]; then 150*7ae52a3dSSasha Levin echo "Test case $((i+1)) FAILED" 151*7ae52a3dSSasha Levin echo "Input: ${test_cases[$i]}" 152*7ae52a3dSSasha Levin echo "Expected output: '${expected[$i]}'" 153*7ae52a3dSSasha Levin echo "Got output: '$result'" 154*7ae52a3dSSasha Levin echo "Expected exit code: ${expected_exit_codes[$i]}" 155*7ae52a3dSSasha Levin echo "Got exit code: $exit_code" 156*7ae52a3dSSasha Levin failed=1 157*7ae52a3dSSasha Levin else 158*7ae52a3dSSasha Levin echo "Test case $((i+1)) PASSED" 159*7ae52a3dSSasha Levin fi 160*7ae52a3dSSasha Levin done 161*7ae52a3dSSasha Levin 162*7ae52a3dSSasha Levin if [ $failed -eq 0 ]; then 163*7ae52a3dSSasha Levin echo "All tests passed!" 164*7ae52a3dSSasha Levin exit 0 165*7ae52a3dSSasha Levin else 166*7ae52a3dSSasha Levin echo "Some tests failed!" 167*7ae52a3dSSasha Levin exit 1 168*7ae52a3dSSasha Levin fi 169*7ae52a3dSSasha Levin} 170*7ae52a3dSSasha Levin 171*7ae52a3dSSasha Levin# Check for selftest 172*7ae52a3dSSasha Levinif [ "$1" = "--selftest" ]; then 173*7ae52a3dSSasha Levin run_selftest 174*7ae52a3dSSasha Levin exit $? 175*7ae52a3dSSasha Levinfi 176*7ae52a3dSSasha Levin 177*7ae52a3dSSasha Levin# Handle --force flag 178*7ae52a3dSSasha Levinforce="" 179*7ae52a3dSSasha Levinif [ "$1" = "--force" ]; then 180*7ae52a3dSSasha Levin force="--force" 181*7ae52a3dSSasha Levin shift 182*7ae52a3dSSasha Levinfi 183*7ae52a3dSSasha Levin 184*7ae52a3dSSasha Levin# Verify arguments 185*7ae52a3dSSasha Levinif [ $# -eq 0 ]; then 186*7ae52a3dSSasha Levin usage 187*7ae52a3dSSasha Levinfi 188*7ae52a3dSSasha Levin 189*7ae52a3dSSasha Levin# Skip validation in force mode 190*7ae52a3dSSasha Levinif [ -z "$force" ]; then 191*7ae52a3dSSasha Levin # Validate that the first argument matches at least one git commit 192*7ae52a3dSSasha Levin if [ "$(git rev-parse --disambiguate="$1" 2>/dev/null | wc -l)" -eq 0 ]; then 193*7ae52a3dSSasha Levin echo "Error: '$1' does not match any git commit" 194*7ae52a3dSSasha Levin exit 1 195*7ae52a3dSSasha Levin fi 196*7ae52a3dSSasha Levinfi 197*7ae52a3dSSasha Levin 198*7ae52a3dSSasha Levingit_resolve_commit $force "$@" 199*7ae52a3dSSasha Levinexit $? 200