#!/bin/bash
# Create a commit or stage files via fzf selection
# If the first arg is "add", files are staged rather than committed.
# All remaining args are passed to the last git command (add or commit).
# TODO fix broken prep-commit-msg hook when there is no unifying path
# TODO fix broken alt-enter not opening editor --bind='alt-enter:change-prompt(hi>)'

# using bash because of pipefail
set -eo pipefail

fzfpipe() {
	# Take nul-separated input from git-status short/porcelain
	# and return a newline-separated list of selected files
	cut -z -c2- |
		git fzf-diff --read0 -d' ' --nth=2.. --bind="alt-enter:execute($EDITOR '$(git rev-parse --show-toplevel)/{2..}')" \
			--preview="test {1} != \? && git diff --color HEAD --unified=4 -- {2..} | $(git config interactive.diffFilter | grep . || echo $PAGER) || find {2..} -type f | xargs -I% diff --recursive --color=always -u /dev/null %" |
		cut -c3-
}

test $(git ls-tree HEAD "$PWD" 2>/dev/null | wc -l) -gt 1 && wd=$PWD
cd "$(git rev-parse --show-toplevel)"
prefix="/tmp/git/fuzz"
mkdir -p "$prefix"
case "$1" in
	(add) shift
		git status -z --porcelain --no-renames --untracked-files=all $wd |
			grep -zv '^\\w ' | fzfpipe |
			xargs -rd '\n' -L 1 git -c advice.addEmptyPathspec=false add --verbose "$@";;
	(*)	git status -z --porcelain --no-renames $wd |
			sed -z 's/^\\(\\w\\) / \\1/' | fzfpipe >"$prefix/files"
			cat "$prefix/files" | xargs -rd '\n' ls -fd 2>/dev/null >"$prefix/files-existing" || true
			git -c advice.addEmptyPathspec=false add --intent-to-add --pathspec-from-file="$prefix/files-existing"
			git commit -v --only --pathspec-from-file="$prefix/files" "$@";;
esac
