| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201 |
- #!/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 $?
|