Created
August 8, 2024 11:01
-
-
Save codeinthehole/903ed1df376aad09322d3a8bc2fec3cf to your computer and use it in GitHub Desktop.
Custom version of `git absorb` which autosquashes unstaged changes
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env bash | |
# | |
# Try and squash unstaged changes into existing branch commits. | |
# | |
# This command examines each unstaged file and attempts to create a fix-up | |
# commit to squash it into its natural parent in the current branch. | |
# | |
# - If it's able to do this for all modified files, the fix-up files are | |
# automatically squashed in. | |
# | |
# - If it's unable to do this, fix-up commits are created but not autosquashed | |
# and the problematic files have their git history printed to stdout. | |
set -euo pipefail | |
main() { | |
# Track how many files couldn't be fixed up. | |
local num_unfixed_files=0 | |
local delta | |
changed_filepaths | while read -r filepath; | |
do | |
delta=$(analyse_filepath "$filepath") | |
num_unfixed_files=$((num_unfixed_files + delta)) | |
done | |
if [[ $num_unfixed_files -eq 0 ]] | |
then | |
# All changes have a fix-up commit - let's autosquash! | |
printf "Autosquashing" >&2 | |
# Specify a no-op editor as we don't want need to be interactive. | |
GIT_SEQUENCE_EDITOR=true git rebase -i --autosquash `git merge-base head origin/$(git defaultbranch)` | |
fi | |
} | |
# Emit a stream of changed filepaths. | |
changed_filepaths() { | |
git status -sb | awk '/^ M/ { print substr($0, 4); }' | sed 's/^"\|"$//g' | |
} | |
# Attempt to create a fix-up commit for the passed filepath. Prints 1 if a | |
# fix-up commit couldn't be created. | |
analyse_filepath() { | |
local filepath="$1" | |
local num_unfixed_files=0 | |
printf "Looking for previous commits for %s - " "$filepath" >&2 | |
# Count commits on this branch that touch the passed filepath. | |
commit_hashes=$(filepath_commit_hashes "$filepath") | |
num_commits=$(echo -e "$commit_hashes" | wc -l) | |
if [ "$num_commits" -eq 1 ] | |
then | |
# Only one commit touch the filepath. We assume the unstaged changes | |
# can be squashed in so we create a fix-up commit. | |
printf "creating fixup commit\n" >&2 | |
git add "$filepath" >&2 | |
git commit --no-verify --fixup="$commit_hashes" > /dev/null 2>&1 | |
else | |
# More than one commit touched the filepath. We don't know which of the | |
# commits to squash the changes into so we show commits to help decide. | |
printf "found %s commits that touched file\n" "$num_commits" >&2 | |
num_unfixed_files=1 | |
echo "$commit_hashes" | while read -r sha; | |
do | |
git show --color --pretty=format:"%h - %s" "$sha" -- "$filepath" >&2 | |
done | |
fi | |
echo "$num_unfixed_files" | |
} | |
# Emit the commit hashes from this branch that touched the passed filepath. | |
filepath_commit_hashes() { | |
git log --format="%H" origin/$(git defaultbranch).. -- "$1" | |
} | |
main |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment