11#! /usr/bin/env bash
22# git-fixup (https://github.com/keis/git-fixup)
3+ # We cannot set -u, because included git libraries don't support it.
4+ set -e
35
6+ # shellcheck disable=SC2034
47OPTIONS_SPEC=" \
58git fixup [options] [<ref>]
69--
@@ -16,7 +19,9 @@ n,no-verify Bypass the pre-commit and commit-msg hooks
1619b,base=rev Use <rev> as base of the revision range for the search
1720A,all Show all candidates
1821"
22+ # shellcheck disable=SC2034
1923SUBDIRECTORY_OK=yes
24+ # shellcheck disable=SC1091
2025. " $( git --exec-path) /git-sh-setup"
2126
2227# Define a sed program that turns `git diff` output into a stream of filenames
@@ -30,13 +35,13 @@ grok_diff='/^--- .*/p ;
3035function fixup_candidates_lines () {
3136 git diff --cached -U1 --no-prefix | sed -n " $grok_diff " | (
3237 file=' '
33- while read offs len; do
34- if test " $offs " == ' ---' ; then
38+ while read -r offs len ; do
39+ if [ " $offs " = ' ---' ] ; then
3540 file=" $len "
3641 else
37- if test " $len " ! = ' 0' ; then
38- if test " $file " ! = ' /dev/null' ; then
39- git blame -sl -L " $offs ,+$len " $rev_range -- " $file "
42+ if [ " $len " != ' 0' ] ; then
43+ if [ " $file " != ' /dev/null' ] ; then
44+ git blame -sl -L " $offs ,+$len " " $rev_range " -- " $file "
4045 fi
4146 fi
4247 fi
@@ -48,17 +53,15 @@ function fixup_candidates_lines () {
4853# staged changes
4954function fixup_candidates_files () {
5055 git diff --cached --name-only | (
51- while read file; do
52- git rev-list $rev_range -- $file \
53- | grep -v -f <( git rev-list -E --grep=' ^(fixup|squash)' $rev_range -- $file ) \
54- | head -n1
56+ while read -r file; do
57+ git rev-list -n 1 -E --invert-grep --grep=' ^(fixup|squash)' " $rev_range " -- " $file "
5558 done
5659 ) | sed ' s/^/F /g'
5760}
5861
5962# Produce suggestion of all commits in $rev_range
6063function fixup_candidates_all_commits () {
61- git rev-list $rev_range | sed ' s/^/F /g'
64+ git rev-list " $rev_range " | sed ' s/^/F /g'
6265}
6366
6467# Pretty print details of a commit
@@ -74,39 +77,41 @@ function call_commit() {
7477 local flag=$op
7578 local target=$1
7679
77- if test " $op " == " amend" ; then
80+ if [ " $op " = " amend" ] ; then
7881 flag=fixup
7982 target=" amend:$target "
8083 fi
8184
82- git commit ${git_commit_args[@]} --$flag =$target || die
85+ # shellcheck disable=SC2086
86+ git commit " ${git_commit_args[@]} " " --$flag =$target " || die
8387}
8488
8589# Call git rebase
8690function call_rebase() {
8791 local target=$1
8892
8993 # If our target-commit has a parent, we call a rebase with that
90- if git rev-parse --quiet --verify $target ~1^{commit}; then
94+ # shellcheck disable=SC1083
95+ if git rev-parse --quiet --verify " $target " ~1^{commit} ; then
9196 git rebase --interactive --autosquash " $target ~1"
9297 # If our target-commit exists but has no parents, it must be the very first commit
9398 # the repo. We simply call a rebase with --root
94- elif git rev-parse --quiet --verify $target ^{commit}; then
99+ elif git rev-parse --quiet --verify " $target " ^{commit} ; then
95100 git rebase --interactive --autosquash --root
96101 fi
97102}
98103
99104# Print list of fixup/squash candidates
100105function print_candidates() {
101106 (
102- if test " $show_all " == " false" ; then
107+ if [ " $show_all " = " false" ] ; then
103108 fixup_candidates_lines
104109 fixup_candidates_files
105110 else
106111 fixup_candidates_all_commits
107112 fi
108- ) | sort -uk2 | while read type sha; do
109- if test " $sha " ! = " " ; then
113+ ) | sort -uk2 | while read -r type sha; do
114+ if [ -n " $sha " ] ; then
110115 print_sha " $sha " " $type "
111116 fi
112117 done
@@ -118,38 +123,38 @@ function fallback_menu() {
118123 read -d ' ' -ra options
119124 PS3=" Which commit should I $op ? "
120125 select line in " ${options[@]} " ; do
121- if test -z " $line " ; then
122- declare -a ' args=(' " $REPLY " ' ) '
126+ if [ -z " $line " ] ; then
127+ declare -a args=(" $REPLY " )
123128 case ${args[0]} in
124129 quit|q)
125130 echo " Alright, no action taken." >&2
126131 break
127132 ;;
128133 show|s)
129- idx=$(( ${ args[1]} - 1 ))
130- if test $idx -ge 0; then
131- git show ${options[$idx]%% * } >&2
134+ idx=$(( args[1 ] - 1 ))
135+ if [ " $idx " -ge 0 ] ; then
136+ git show " ${options[$idx]%% * } " >&2
132137 fi
133138 ;;
134139 help|h)
135140 local fmt=" %s\n %s\n"
136- printf $fmt " <n>" " $op the <n>-th commit from the list" >&2
137- printf $fmt " s[how] <n>" " show the <n>-th commit from the list" >&2
138- printf $fmt " q[uit]" " abort operation" >&2
139- printf $fmt " h[elp]" " show this help message" >&2
141+ printf " $fmt " " <n>" " $op the <n>-th commit from the list" >&2
142+ printf " $fmt " " s[how] <n>" " show the <n>-th commit from the list" >&2
143+ printf " $fmt " " q[uit]" " abort operation" >&2
144+ printf " $fmt " " h[elp]" " show this help message" >&2
140145 ;;
141146 esac
142147 else
143- echo $line
148+ echo " $line "
144149 break
145150 fi
146151 done < /dev/tty
147152 )
148153}
149154
150155show_menu () {
151- if test -n " $fixup_menu " ; then
152- eval command $fixup_menu
156+ if [ -n " $fixup_menu " ] ; then
157+ eval command " $fixup_menu "
153158 else
154159 fallback_menu
155160 fi
@@ -164,7 +169,7 @@ create_commit=${GITFIXUPCOMMIT:-$(git config --default=false --type bool fixup.c
164169base=${GITFIXUPBASE:- $(git config --default=" " fixup.base)}
165170show_all=false
166171
167- while test $# -gt 0; do
172+ while [ $# -gt 0 ] ; do
168173 case " $1 " in
169174 -s|--squash)
170175 op=" squash"
@@ -188,11 +193,11 @@ while test $# -gt 0; do
188193 rebase=false
189194 ;;
190195 -n|--no-verify)
191- git_commit_args+=($1 )
196+ git_commit_args+=(" $1 " )
192197 ;;
193198 -b|--base)
194199 shift
195- if test $# -eq 0; then
200+ if [ $# -eq 0 ] ; then
196201 die " --base requires an argument"
197202 fi
198203 base=" $1 "
@@ -209,59 +214,59 @@ while test $# -gt 0; do
209214done
210215
211216target=" $1 "
212- if test $# -gt 1; then
217+ if [ $# -gt 1 ] ; then
213218 die " Pass only one ref, please"
214219fi
215220
216- if ! test -z " $target " ; then
217- call_commit $target
218- if test " $rebase " == " true" ; then
219- call_rebase $target
221+ if [ -n " $target " ] ; then
222+ call_commit " $target "
223+ if [ " $rebase " = " true" ] ; then
224+ call_rebase " $target "
220225 fi
221226 exit
222227fi
223228
224- if git diff --cached --quiet; then
229+ if git diff --cached --quiet ; then
225230 die ' No staged changes. Use git add -p to add them.'
226231fi
227232
228233cd_to_toplevel
229234
230- if test " $base " == " closest" ; then
235+ if [ " $base " = " closest" ] ; then
231236 base=$( git for-each-ref \
232237 --merged HEAD~1 \
233238 --sort=-committerdate \
234239 refs/heads/ \
235240 --count 1 \
236241 --format=' %(objectname)' \
237242 )
238- if test -z " $base " ; then
243+ if [ -z " $base " ] ; then
239244 die " Could not find the ancestor branch"
240245 fi
241246fi
242247
243- if test -z " $base " ; then
244- upstream=` git rev-parse @{upstream} 2> /dev/null`
245- head=` git rev-parse HEAD 2> /dev/null`
246- if test -n " $upstream " -a " $upstream " ! = " $head " ; then
248+ if [ -z " $base " ] ; then
249+ upstream=$( git rev-parse " @{upstream}" 2> /dev/null)
250+ head=$( git rev-parse HEAD 2> /dev/null)
251+ if [ -n " $upstream " ] && [ " $upstream " != " $head " ] ; then
247252 base=" $upstream "
248253 fi
249254fi
250255
251- if test -n " $base " ; then
256+ if [ -n " $base " ] ; then
252257 rev_range=" $base ..HEAD"
253258else
254259 rev_range=" HEAD"
255260fi
256261
257- if test " $create_commit " == " true" ; then
262+ if [ " $create_commit " = " true" ] ; then
258263 target=$( print_candidates | show_menu)
259- if test -z " $target " ; then
264+ if [ -z " $target " ] ; then
260265 exit
261266 fi
262- call_commit ${target%% * }
263- if test " $rebase " == " true" ; then
264- call_rebase ${target%% * }
267+ call_commit " ${target%% * } "
268+ if [ " $rebase " = " true" ] ; then
269+ call_rebase " ${target%% * } "
265270 fi
266271else
267272 print_candidates
0 commit comments