201 lines · 5.8 KB · Shell
RawDownload
#!/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 patternconvert_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 selftestif [ "$1" = "--selftest" ]; then	run_selftest	exit $?fi # Handle --force flagforce=""if [ "$1" = "--force" ]; then	force="--force"	shiftfi # Verify argumentsif [ $# -eq 0 ]; then	usagefi # Skip validation in force modeif [ -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	fifi git_resolve_commit $force "$@"exit $?