17ae52a3dSSasha Levin#!/bin/bash 2*d0fd663aSSasha Levin# SPDX-License-Identifier: GPL-2.0 3*d0fd663aSSasha Levin# (c) 2025, Sasha Levin <sashal@kernel.org> 47ae52a3dSSasha Levin 57ae52a3dSSasha Levinusage() { 67ae52a3dSSasha Levin echo "Usage: $(basename "$0") [--selftest] [--force] <commit-id> [commit-subject]" 77ae52a3dSSasha Levin echo "Resolves a short git commit ID to its full SHA-1 hash, particularly useful for fixing references in commit messages." 87ae52a3dSSasha Levin echo "" 97ae52a3dSSasha Levin echo "Arguments:" 107ae52a3dSSasha Levin echo " --selftest Run self-tests" 117ae52a3dSSasha Levin echo " --force Try to find commit by subject if ID lookup fails" 127ae52a3dSSasha Levin echo " commit-id Short git commit ID to resolve" 137ae52a3dSSasha Levin echo " commit-subject Optional commit subject to help resolve between multiple matches" 147ae52a3dSSasha Levin exit 1 157ae52a3dSSasha Levin} 167ae52a3dSSasha Levin 177ae52a3dSSasha Levin# Convert subject with ellipsis to grep pattern 187ae52a3dSSasha Levinconvert_to_grep_pattern() { 197ae52a3dSSasha Levin local subject="$1" 207ae52a3dSSasha Levin # First escape ALL regex special characters 217ae52a3dSSasha Levin local escaped_subject 227ae52a3dSSasha Levin escaped_subject=$(printf '%s\n' "$subject" | sed 's/[[\.*^$()+?{}|]/\\&/g') 237ae52a3dSSasha Levin # Also escape colons, parentheses, and hyphens as they are special in our context 247ae52a3dSSasha Levin escaped_subject=$(echo "$escaped_subject" | sed 's/[:-]/\\&/g') 257ae52a3dSSasha Levin # Then convert escaped ... sequence to .*? 267ae52a3dSSasha Levin escaped_subject=$(echo "$escaped_subject" | sed 's/\\\.\\\.\\\./.*?/g') 277ae52a3dSSasha Levin echo "^${escaped_subject}$" 287ae52a3dSSasha Levin} 297ae52a3dSSasha Levin 307ae52a3dSSasha Levingit_resolve_commit() { 317ae52a3dSSasha Levin local force=0 327ae52a3dSSasha Levin if [ "$1" = "--force" ]; then 337ae52a3dSSasha Levin force=1 347ae52a3dSSasha Levin shift 357ae52a3dSSasha Levin fi 367ae52a3dSSasha Levin 377ae52a3dSSasha Levin # Split input into commit ID and subject 387ae52a3dSSasha Levin local input="$*" 397ae52a3dSSasha Levin local commit_id="${input%% *}" 407ae52a3dSSasha Levin local subject="" 417ae52a3dSSasha Levin 427ae52a3dSSasha Levin # Extract subject if present (everything after the first space) 437ae52a3dSSasha Levin if [[ "$input" == *" "* ]]; then 447ae52a3dSSasha Levin subject="${input#* }" 457ae52a3dSSasha Levin # Strip the ("...") quotes if present 467ae52a3dSSasha Levin subject="${subject#*(\"}" 477ae52a3dSSasha Levin subject="${subject%\")*}" 487ae52a3dSSasha Levin fi 497ae52a3dSSasha Levin 507ae52a3dSSasha Levin # Get all possible matching commit IDs 517ae52a3dSSasha Levin local matches 527ae52a3dSSasha Levin readarray -t matches < <(git rev-parse --disambiguate="$commit_id" 2>/dev/null) 537ae52a3dSSasha Levin 547ae52a3dSSasha Levin # Return immediately if we have exactly one match 557ae52a3dSSasha Levin if [ ${#matches[@]} -eq 1 ]; then 567ae52a3dSSasha Levin echo "${matches[0]}" 577ae52a3dSSasha Levin return 0 587ae52a3dSSasha Levin fi 597ae52a3dSSasha Levin 607ae52a3dSSasha Levin # If no matches and not in force mode, return failure 617ae52a3dSSasha Levin if [ ${#matches[@]} -eq 0 ] && [ $force -eq 0 ]; then 627ae52a3dSSasha Levin return 1 637ae52a3dSSasha Levin fi 647ae52a3dSSasha Levin 657ae52a3dSSasha Levin # If we have a subject, try to find a match with that subject 667ae52a3dSSasha Levin if [ -n "$subject" ]; then 677ae52a3dSSasha Levin # Convert subject with possible ellipsis to grep pattern 687ae52a3dSSasha Levin local grep_pattern 697ae52a3dSSasha Levin grep_pattern=$(convert_to_grep_pattern "$subject") 707ae52a3dSSasha Levin 717ae52a3dSSasha Levin # In force mode with no ID matches, use git log --grep directly 727ae52a3dSSasha Levin if [ ${#matches[@]} -eq 0 ] && [ $force -eq 1 ]; then 737ae52a3dSSasha Levin # Use git log to search, but filter to ensure subject matches exactly 747ae52a3dSSasha Levin local match 757ae52a3dSSasha Levin match=$(git log --format="%H %s" --grep="$grep_pattern" --perl-regexp -10 | \ 767ae52a3dSSasha Levin while read -r hash subject; do 777ae52a3dSSasha Levin if echo "$subject" | grep -qP "$grep_pattern"; then 787ae52a3dSSasha Levin echo "$hash" 797ae52a3dSSasha Levin break 807ae52a3dSSasha Levin fi 817ae52a3dSSasha Levin done) 827ae52a3dSSasha Levin if [ -n "$match" ]; then 837ae52a3dSSasha Levin echo "$match" 847ae52a3dSSasha Levin return 0 857ae52a3dSSasha Levin fi 867ae52a3dSSasha Levin else 877ae52a3dSSasha Levin # Normal subject matching for existing matches 887ae52a3dSSasha Levin for match in "${matches[@]}"; do 897ae52a3dSSasha Levin if git log -1 --format="%s" "$match" | grep -qP "$grep_pattern"; then 907ae52a3dSSasha Levin echo "$match" 917ae52a3dSSasha Levin return 0 927ae52a3dSSasha Levin fi 937ae52a3dSSasha Levin done 947ae52a3dSSasha Levin fi 957ae52a3dSSasha Levin fi 967ae52a3dSSasha Levin 977ae52a3dSSasha Levin # No match found 987ae52a3dSSasha Levin return 1 997ae52a3dSSasha Levin} 1007ae52a3dSSasha Levin 1017ae52a3dSSasha Levinrun_selftest() { 1027ae52a3dSSasha Levin local test_cases=( 1037ae52a3dSSasha Levin '00250b5 ("MAINTAINERS: add new Rockchip SoC list")' 1047ae52a3dSSasha Levin '0037727 ("KVM: selftests: Convert xen_shinfo_test away from VCPU_ID")' 1057ae52a3dSSasha Levin 'ffef737 ("net/tls: Fix skb memory leak when running kTLS traffic")' 1067ae52a3dSSasha Levin 'd3d7 ("cifs: Improve guard for excluding $LXDEV xattr")' 1077ae52a3dSSasha Levin 'dbef ("Rename .data.once to .data..once to fix resetting WARN*_ONCE")' 1087ae52a3dSSasha Levin '12345678' # Non-existent commit 1097ae52a3dSSasha Levin '12345 ("I'\''m a dummy commit")' # Valid prefix but wrong subject 1107ae52a3dSSasha Levin '--force 99999999 ("net/tls: Fix skb memory leak when running kTLS traffic")' # Force mode with non-existent ID but valid subject 1117ae52a3dSSasha Levin '83be ("firmware: ... auto-update: fix poll_complete() ... errors")' # Wildcard test 1127ae52a3dSSasha Levin '--force 999999999999 ("firmware: ... auto-update: fix poll_complete() ... errors")' # Force mode wildcard test 1137ae52a3dSSasha Levin ) 1147ae52a3dSSasha Levin 1157ae52a3dSSasha Levin local expected=( 1167ae52a3dSSasha Levin "00250b529313d6262bb0ebbd6bdf0a88c809f6f0" 1177ae52a3dSSasha Levin "0037727b3989c3fe1929c89a9a1dfe289ad86f58" 1187ae52a3dSSasha Levin "ffef737fd0372ca462b5be3e7a592a8929a82752" 1197ae52a3dSSasha Levin "d3d797e326533794c3f707ce1761da7a8895458c" 1207ae52a3dSSasha Levin "dbefa1f31a91670c9e7dac9b559625336206466f" 1217ae52a3dSSasha Levin "" # Expect empty output for non-existent commit 1227ae52a3dSSasha Levin "" # Expect empty output for wrong subject 1237ae52a3dSSasha Levin "ffef737fd0372ca462b5be3e7a592a8929a82752" # Should find commit by subject in force mode 1247ae52a3dSSasha Levin "83beece5aff75879bdfc6df8ba84ea88fd93050e" # Wildcard test 1257ae52a3dSSasha Levin "83beece5aff75879bdfc6df8ba84ea88fd93050e" # Force mode wildcard test 1267ae52a3dSSasha Levin ) 1277ae52a3dSSasha Levin 1287ae52a3dSSasha Levin local expected_exit_codes=( 1297ae52a3dSSasha Levin 0 1307ae52a3dSSasha Levin 0 1317ae52a3dSSasha Levin 0 1327ae52a3dSSasha Levin 0 1337ae52a3dSSasha Levin 0 1347ae52a3dSSasha Levin 1 # Expect failure for non-existent commit 1357ae52a3dSSasha Levin 1 # Expect failure for wrong subject 1367ae52a3dSSasha Levin 0 # Should succeed in force mode 1377ae52a3dSSasha Levin 0 # Should succeed with wildcard 1387ae52a3dSSasha Levin 0 # Should succeed with force mode and wildcard 1397ae52a3dSSasha Levin ) 1407ae52a3dSSasha Levin 1417ae52a3dSSasha Levin local failed=0 1427ae52a3dSSasha Levin 1437ae52a3dSSasha Levin echo "Running self-tests..." 1447ae52a3dSSasha Levin for i in "${!test_cases[@]}"; do 1457ae52a3dSSasha Levin # Capture both output and exit code 1467ae52a3dSSasha Levin local result 1477ae52a3dSSasha Levin result=$(git_resolve_commit ${test_cases[$i]}) # Removed quotes to allow --force to be parsed 1487ae52a3dSSasha Levin local exit_code=$? 1497ae52a3dSSasha Levin 1507ae52a3dSSasha Levin # Check both output and exit code 1517ae52a3dSSasha Levin if [ "$result" != "${expected[$i]}" ] || [ $exit_code != ${expected_exit_codes[$i]} ]; then 1527ae52a3dSSasha Levin echo "Test case $((i+1)) FAILED" 1537ae52a3dSSasha Levin echo "Input: ${test_cases[$i]}" 1547ae52a3dSSasha Levin echo "Expected output: '${expected[$i]}'" 1557ae52a3dSSasha Levin echo "Got output: '$result'" 1567ae52a3dSSasha Levin echo "Expected exit code: ${expected_exit_codes[$i]}" 1577ae52a3dSSasha Levin echo "Got exit code: $exit_code" 1587ae52a3dSSasha Levin failed=1 1597ae52a3dSSasha Levin else 1607ae52a3dSSasha Levin echo "Test case $((i+1)) PASSED" 1617ae52a3dSSasha Levin fi 1627ae52a3dSSasha Levin done 1637ae52a3dSSasha Levin 1647ae52a3dSSasha Levin if [ $failed -eq 0 ]; then 1657ae52a3dSSasha Levin echo "All tests passed!" 1667ae52a3dSSasha Levin exit 0 1677ae52a3dSSasha Levin else 1687ae52a3dSSasha Levin echo "Some tests failed!" 1697ae52a3dSSasha Levin exit 1 1707ae52a3dSSasha Levin fi 1717ae52a3dSSasha Levin} 1727ae52a3dSSasha Levin 1737ae52a3dSSasha Levin# Check for selftest 1747ae52a3dSSasha Levinif [ "$1" = "--selftest" ]; then 1757ae52a3dSSasha Levin run_selftest 1767ae52a3dSSasha Levin exit $? 1777ae52a3dSSasha Levinfi 1787ae52a3dSSasha Levin 1797ae52a3dSSasha Levin# Handle --force flag 1807ae52a3dSSasha Levinforce="" 1817ae52a3dSSasha Levinif [ "$1" = "--force" ]; then 1827ae52a3dSSasha Levin force="--force" 1837ae52a3dSSasha Levin shift 1847ae52a3dSSasha Levinfi 1857ae52a3dSSasha Levin 1867ae52a3dSSasha Levin# Verify arguments 1877ae52a3dSSasha Levinif [ $# -eq 0 ]; then 1887ae52a3dSSasha Levin usage 1897ae52a3dSSasha Levinfi 1907ae52a3dSSasha Levin 1917ae52a3dSSasha Levin# Skip validation in force mode 1927ae52a3dSSasha Levinif [ -z "$force" ]; then 1937ae52a3dSSasha Levin # Validate that the first argument matches at least one git commit 1947ae52a3dSSasha Levin if [ "$(git rev-parse --disambiguate="$1" 2>/dev/null | wc -l)" -eq 0 ]; then 1957ae52a3dSSasha Levin echo "Error: '$1' does not match any git commit" 1967ae52a3dSSasha Levin exit 1 1977ae52a3dSSasha Levin fi 1987ae52a3dSSasha Levinfi 1997ae52a3dSSasha Levin 2007ae52a3dSSasha Levingit_resolve_commit $force "$@" 2017ae52a3dSSasha Levinexit $? 202