On Tue, Mar 11, 2025 at 12:53:36PM -0400, Sasha Levin wrote: > Introduce git-resolve.sh, a tool that resolves short git commit IDs to their > full SHA-1 hash. This is particularly useful for navigating references in commit > messages and verifying Fixes tags. > > When faced with ambiguous commit IDs or imprecise references in messages, > this tool can help by resolving commit hashes based on not just the ID > itself but also the commit subject, making it more robust than standard > git rev-parse. > > This is especially valuable for maintainers who need to verify Fixes tags > or cross-reference commits. Unlike proposals to add dates to Fixes tags > (which would break existing tooling), this script provides a way to > disambiguate commits without changing the established tag format. > > The script includes several features: > - Resolves short commit IDs to full SHA-1 hashes > - Uses commit subjects to disambiguate between multiple potential matches > - Supports wildcard patterns in subjects with ellipsis (...) > - Provides a force mode to attempt resolution by subject when ID lookup fails > - Includes comprehensive self-tests > > Signed-off-by: Sasha Levin <sashal@xxxxxxxxxx> > --- > scripts/git-resolve.sh | 199 +++++++++++++++++++++++++++++++++++++++++ > 1 file changed, 199 insertions(+) > create mode 100755 scripts/git-resolve.sh > > diff --git a/scripts/git-resolve.sh b/scripts/git-resolve.sh ... > +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 I suppose it doesn't matter in practice, but it seems somewhat inconsistent to declare match as local here... > + 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 ... but not here. > + 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 > +} ...