File Index
Files and directories
Sign in
FILE

git-resolve.sh

File preview with syntax highlighting.

linux-6.19.14/scripts/git-resolve.sh BASH
  1#!/bin/bash
  2# SPDX-License-Identifier: GPL-2.0
  3# (c) 2025, Sasha Levin <sashal@kernel.org>
  4
  5usage() {
  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# Convert subject with ellipsis to grep pattern
 18convert_to_grep_pattern() {
 19	local subject="$1"
 20	# First escape ALL regex special characters
 21	local escaped_subject
 22	escaped_subject=$(printf '%s\n' "$subject" | sed 's/[[\.*^$()+?{}|]/\\&/g')
 23	# Also escape colons, parentheses, and hyphens as they are special in our context
 24	escaped_subject=$(echo "$escaped_subject" | sed 's/[:-]/\\&/g')
 25	# Then convert escaped ... sequence to .*?
 26	escaped_subject=$(echo "$escaped_subject" | sed 's/\\\.\\\.\\\./.*?/g')
 27	echo "^${escaped_subject}$"
 28}
 29
 30git_resolve_commit() {
 31	local force=0
 32	if [ "$1" = "--force" ]; then
 33		force=1
 34		shift
 35	fi
 36
 37	# Split input into commit ID and subject
 38	local input="$*"
 39	local commit_id="${input%% *}"
 40	local subject=""
 41
 42	# Extract subject if present (everything after the first space)
 43	if [[ "$input" == *" "* ]]; then
 44		subject="${input#* }"
 45		# Strip the ("...") quotes if present
 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
101run_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")'  # Valid prefix but wrong subject
110		'--force 99999999 ("net/tls: Fix skb memory leak when running kTLS traffic")'  # Force mode with non-existent ID but valid subject
111		'83be ("firmware: ... auto-update: fix poll_complete() ... errors")'  # Wildcard test
112		'--force 999999999999 ("firmware: ... auto-update: fix poll_complete() ... errors")'  # Force mode wildcard test
113	)
114
115	local expected=(
116		"00250b529313d6262bb0ebbd6bdf0a88c809f6f0"
117		"0037727b3989c3fe1929c89a9a1dfe289ad86f58"
118		"ffef737fd0372ca462b5be3e7a592a8929a82752"
119		"d3d797e326533794c3f707ce1761da7a8895458c"
120		"dbefa1f31a91670c9e7dac9b559625336206466f"
121		""  # Expect empty output for non-existent commit
122		""  # Expect empty output for wrong subject
123		"ffef737fd0372ca462b5be3e7a592a8929a82752"  # Should find commit by subject in force mode
124		"83beece5aff75879bdfc6df8ba84ea88fd93050e"  # Wildcard test
125		"83beece5aff75879bdfc6df8ba84ea88fd93050e"  # Force mode wildcard test
126	)
127
128	local expected_exit_codes=(
129		0
130		0
131		0
132		0
133		0
134		1  # Expect failure for non-existent commit
135		1  # Expect failure for wrong subject
136		0  # Should succeed in force mode
137		0  # Should succeed with wildcard
138		0  # Should succeed with force mode and wildcard
139	)
140
141	local failed=0
142
143	echo "Running self-tests..."
144	for i in "${!test_cases[@]}"; do
145		# Capture both output and exit code
146		local result
147		result=$(git_resolve_commit ${test_cases[$i]})  # Removed quotes to allow --force to be parsed
148		local exit_code=$?
149
150		# Check both output and exit code
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
174if [ "$1" = "--selftest" ]; then
175	run_selftest
176	exit $?
177fi
178
179# Handle --force flag
180force=""
181if [ "$1" = "--force" ]; then
182	force="--force"
183	shift
184fi
185
186# Verify arguments
187if [ $# -eq 0 ]; then
188	usage
189fi
190
191# Skip validation in force mode
192if [ -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
198fi
199
200git_resolve_commit $force "$@"
201exit $?
~/ Read-only file preview.