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