Created
December 31, 2021 10:50
-
-
Save micoli/f6c04e7b8bce1ec23a6ca5f6461bb840 to your computer and use it in GitHub Desktop.
This file contains hidden or 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 | |
| # git-fixup (https://github.com/keis/git-fixup) | |
| SUBDIRECTORY_OK=yes | |
| . "$(git --exec-path)/git-sh-setup" | |
| # Define a sed program that turns `git diff` output into a stream of filenames | |
| # and sections within those files. | |
| grok_diff='/^--- .*/p ; | |
| s/^@@ -\([0-9]*\),\([0-9]*\).*/\1 \2/p' | |
| # Produce suggestion of commits by finding the sections of files with changes | |
| # staged (U1 to diff is used to give some context for when adding items to | |
| # lists etc) and looking up the previous commits touching those sections. | |
| function fixup_candidates_lines () { | |
| git diff --cached -U1 --no-prefix | sed -n "$grok_diff" | ( | |
| file='' | |
| while read offs len; do | |
| if test "$offs" == '---'; then | |
| file="$len" | |
| else | |
| if test "$len" != '0'; then | |
| if test "$file" != '/dev/null'; then | |
| git blame -sl -L "$offs,+$len" $rev_range -- "$file" | |
| fi | |
| fi | |
| fi | |
| done | |
| ) | grep -v "^^" | cut -d' ' -f 1 | sed 's/^/L /g' | |
| } | |
| # Produce suggestion of commits by taking the latest commit to each file with | |
| # staged changes | |
| function fixup_candidates_files () { | |
| git diff --cached --name-only | ( | |
| while read file; do | |
| git rev-list $rev_range -- $file \ | |
| | grep -v -f <(git rev-list -E --grep='^(fixup|squash)' $rev_range -- $file) \ | |
| | head -n1 | |
| done | |
| ) | sed 's/^/F /g' | |
| } | |
| # Pretty print details of a commit | |
| function print_sha () { | |
| local sha=$1 | |
| local type=$2 | |
| git --no-pager log --date=format:'%Y-%m-%d %H:%M:%S' --format="%H %ad [$type] %s <%ae>" -n 1 "$sha" | |
| } | |
| # Call git commit | |
| function call_commit() { | |
| local target=$1 | |
| git commit --$op=$target | |
| } | |
| # Print list of fixup/squash candidates | |
| function print_candidates() { | |
| ( | |
| fixup_candidates_lines | |
| fixup_candidates_files | |
| ) | sort -uk2 | while read type sha; do | |
| if test "$sha" != ""; then | |
| print_sha "$sha" "$type" | |
| fi | |
| done | sort --key 2 --reverse | |
| } | |
| function fallback_menu() { | |
| ( | |
| readarray -t options | |
| IFS=$'\n' | |
| PS3="Which commit should I $op? " | |
| select line in "${options[@]}"; do | |
| if test -z "$line"; then | |
| declare -a 'args=('"$REPLY"')' | |
| case ${args[0]} in | |
| quit|q) | |
| echo "Alright, no action taken." >&2 | |
| break | |
| ;; | |
| show|s) | |
| idx=$((${args[1]} - 1)) | |
| if test $idx -ge 0; then | |
| git show ${options[$idx]%% *} >&2 | |
| fi | |
| ;; | |
| help|h) | |
| local fmt="%s\n %s\n" | |
| printf $fmt "<n>" "$op the <n>-th commit from the list" >&2 | |
| printf $fmt "s[how] <n>" "show the <n>-th commit from the list" >&2 | |
| printf $fmt "q[uit]" "abort operation" >&2 | |
| printf $fmt "h[elp]" "show this help message" >&2 | |
| ;; | |
| esac | |
| else | |
| echo $line | |
| break | |
| fi | |
| done < /dev/tty | |
| ) | |
| } | |
| op="fixup" | |
| target= | |
| create_commit=$(git config --default=false --type bool fixup.commit) | |
| while test $# -gt 0; do | |
| case "$1" in | |
| -s|--squash) | |
| op="squash" | |
| shift | |
| ;; | |
| -c|--commit) | |
| create_commit=true | |
| shift | |
| ;; | |
| --no-commit) | |
| create_commit=false | |
| shift | |
| ;; | |
| -*) | |
| echo "Invalid option: $1" >&2 | |
| exit 1 | |
| ;; | |
| *) | |
| if ! test -z "$target"; then | |
| echo "Pass only one ref, please." >&2 | |
| exit 1 | |
| fi | |
| target="$1" | |
| shift | |
| ;; | |
| esac | |
| done | |
| if ! test -z "$target"; then | |
| call_commit $target | |
| exit | |
| fi | |
| if git diff --cached --quiet; then | |
| echo 'No staged changes. Use git add -p to add them.' >&2 | |
| exit 1 | |
| fi | |
| cd_to_toplevel | |
| upstream=`git rev-parse @{upstream} 2>/dev/null` | |
| head=`git rev-parse HEAD 2>/dev/null` | |
| if test -z "$upstream" -o "$upstream" = "$head"; then | |
| rev_range="HEAD" | |
| else | |
| rev_range="@{upstream}..HEAD" | |
| fi | |
| if test "$create_commit" == "true"; then | |
| GITFIXUPMENU=${GITFIXUPMENU:-$(git config --default="" fixup.menu)} | |
| if test -n "$GITFIXUPMENU"; then | |
| menu="eval command $GITFIXUPMENU" | |
| else | |
| menu=fallback_menu | |
| fi | |
| target=$(print_candidates | $menu) | |
| ! test -z "$target" && call_commit ${target%% *} | |
| else | |
| print_candidates | |
| fi |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment