#!/bin/bash # SPDX-License-Identifier: GPL-2.0 # (c) 2025, Sasha Levin usage() { echo "Usage: $(basename "$0") [--selftest] [--force] [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 $?