Previously when calling `glog -- file` with a file that was renamed
sometime in the history, the preview would just be empty for all commits
before the rename, since it's path didn't exist.
Fix this by checking for empty output and falling back to the full patch
in that case.
This also heavily refactors the code around `$fzf_preview` to make it
more readable.
TODO: It would be nice if git-show would fail in this case instead of
just printing nothing and returning zero
183 lines
6.4 KiB
Bash
Executable File
183 lines
6.4 KiB
Bash
Executable File
#!/usr/bin/env zsh
|
|
## Author: druckdev
|
|
## Created: 2020-08-28
|
|
##
|
|
## A TUI for git-log using fzf.
|
|
## Displays git-log in fzf and git-show as preview command for each commit.
|
|
|
|
# TODO: preview breaks when files were passed but they were renamed and have a
|
|
# different name at the point of this commit
|
|
|
|
# extendedglob is necessary for the expansion of the binds array
|
|
emulate -L zsh -o extendedglob
|
|
|
|
# Return if fzf is not available
|
|
if (( ! $+commands[fzf] )); then
|
|
printf "command not found: fzf" >&2
|
|
return 1
|
|
fi
|
|
|
|
# Return if not in git repo
|
|
git rev-parse || return
|
|
|
|
# One line format for fzf list view
|
|
# abbreviated commit hash (yellow), title and ref names
|
|
local formatshort='--pretty=format:%C(yellow)%h %Creset%s%C(auto)%d'
|
|
# Verbose format for the preview window on the right
|
|
local -a format=(
|
|
'--pretty=format:'
|
|
'%C(yellow)' 'Commit: ' '%H' '%n' # commit hash
|
|
'%C(auto)' ' ' '%D' '%n' # ref names (if any)
|
|
'%C(blue)' 'Author: ' '%aN <%aE>' '%n' # author mail
|
|
'%C(red)' 'AuthorDate: ' '%ad' '%n' # author date
|
|
'%C(blue)' 'Commit: ' '%cN <%cE>' '%n' # commiter mail
|
|
'%C(red)' 'CommitDate: ' '%cd' '%n' # commit date
|
|
'%C(blue)' 'Signer: ' '%GS' '%n' # signer name
|
|
'%C(green)' 'Key (%G?): ' '%GK' '%n' # pgp key used to sign
|
|
'%n'
|
|
'%C(reset)%C(bold)' ' %s%C(reset)' '%n' # subject
|
|
'%n'
|
|
'%-b' # body
|
|
'%n'
|
|
)
|
|
format="${(j::)format}"
|
|
|
|
# Ignore the graph part at the beginning, then capture the commit hash and throw
|
|
# away the rest of the line.
|
|
local commit_hash='s/^[^a-f0-9]*([a-f0-9]*).*$/\1/'
|
|
|
|
local dateshort='--date=format:%F' # year
|
|
local date="$dateshort %T %z" # year time timezone
|
|
|
|
# Use git's pager in the preview window (and with it any special highlighting
|
|
# tool, such as diff-so-fancy)
|
|
local pager
|
|
pager="$(git config --get --default="" core.pager)"
|
|
into_pager="${pager:+|} $pager"
|
|
|
|
# NOTE: use read for a bit better readability of the code since less needs
|
|
# quoting and formatting is easier
|
|
read -r -d '' get_hash_cmd <<EOT
|
|
hash="\$(echo -E {} | sed -E "$commit_hash")"; [[ -z "\$hash" ]] ||
|
|
EOT
|
|
read -r -d '' show_cmd <<EOT
|
|
git show "$format" "$date" --color=always --patch-with-stat "\$hash"
|
|
EOT
|
|
show_stat_cmd="${show_cmd/patch-with-}"
|
|
|
|
local -A fzf_preview
|
|
fzf_preview[patch]="$get_hash_cmd $show_cmd $into_pager"
|
|
fzf_preview[stat]="$get_hash_cmd $show_stat_cmd $into_pager"
|
|
|
|
# TODO: Display `...` behind patch-stat if the patch touched more files
|
|
# TODO: Use `-O <file>` for 'patch' command with `-- <file>` argument, so that
|
|
# the passed file is ontop although the full patch is shown
|
|
# TODO: Support -L flag as well (add `-s` for list and `-- <file>` & maybe `-W`
|
|
# for preview)
|
|
# Get file arguments after (and including) `--` and wrap each in quotes
|
|
read -r -d '' 'fzf_preview[files_only]' <<EOT
|
|
$get_hash_cmd {
|
|
{
|
|
out="\$($show_cmd ${(@)${@:${@[(ei)--]}}/(#m)*/'$MATCH'})"
|
|
if [[ \$out ]]; then
|
|
printf '%s' "\$out"
|
|
else
|
|
echo "warning: files were renamed. showing full diff"
|
|
$show_cmd
|
|
fi
|
|
} $into_pager
|
|
}
|
|
EOT
|
|
|
|
# Put the commit hash into the clipboard
|
|
# (If no known clipboard tool is available, just print it)
|
|
local fzf_copy_command="$get_hash_cmd echo -n \"\$hash\""
|
|
if [[ $OSTYPE =~ darwin ]] && (( $+commands[pbcopy] )); then
|
|
fzf_copy_command+=" | pbcopy"
|
|
elif (( $+commands[xclip] )); then
|
|
fzf_copy_command+=" | xclip -selection c"
|
|
fi
|
|
|
|
local -A binds=(
|
|
# See fzf_keys in .zprofile
|
|
"esc" "cancel"
|
|
"home" "first"
|
|
"end" "last"
|
|
"ctrl-d" "half-page-down"
|
|
"ctrl-u" "half-page-up"
|
|
"ctrl-t" "toggle-track"
|
|
# Keep the current line selected while deleting the query
|
|
"bspace" "track-current+backward-delete-char"
|
|
"backward-eof" "untrack-current"
|
|
|
|
# scroll in preview window
|
|
"ctrl-alt-j" "preview-down"
|
|
"ctrl-alt-k" "preview-up"
|
|
# Copy commit hash
|
|
"ctrl-y" "execute@$fzf_copy_command@"
|
|
# Open preview "fullscreen"
|
|
# NOTE: `-+F` is there to negate a possible --quit-if-one-screen
|
|
# TODO: This assumes less to be used in core.pager
|
|
"enter" "execute@$fzf_preview[patch] | $pager -+F@"
|
|
# Preview stats
|
|
"ctrl-s" "change-preview($fzf_preview[stat])"
|
|
# Preview patch
|
|
"ctrl-p" "change-preview($fzf_preview[patch])"
|
|
# Files only
|
|
"ctrl-f" "change-preview($fzf_preview[files_only])"
|
|
# For ctrl-space see below
|
|
)
|
|
|
|
# TODO: Make the --preview argument dependent of --stat flag (i.e.
|
|
# fzf_preview[stat] in this case). It does not really make sense to pass
|
|
# it to `git log` but can be an indicator for the preview function
|
|
|
|
local -a fzf_args=(
|
|
# Understand ansi color escape sequences.
|
|
"--ansi"
|
|
# Expand the binds array in the format "key1:value1,key2:value2".
|
|
"--bind" "${(@kj:,:)binds/(#m)*/$MATCH:$binds[$MATCH]}"
|
|
# Display key-bindings in a sticky header
|
|
"--header" $'\e[38;5;144mEnter\e[38;5;59m: fullscreen, \e[38;5;144mC-y\e[38;5;59m: yank hash, \e[38;5;144mC-Space\e[38;5;59m: cycle preview, \e[38;5;144mC-[spf]\e[38;5;59m: preview stats/patch/files'
|
|
# Keep header above prompt line
|
|
"--header-first"
|
|
# Execute git show on the commit as preview.
|
|
"--preview" "$fzf_preview[files_only]"
|
|
# Reverse the layout so that the newest commit is at the top.
|
|
"--reverse"
|
|
# Do not sort when typing to maintain the sorting by date.
|
|
"--no-sort"
|
|
# Highlight the whole current line similar to vim's 'cursorline
|
|
"--highlight-line"
|
|
)
|
|
|
|
# The preview-window should be placed differently depending on the dimensions of
|
|
# the terminal. It should have at least 152 columns to fit a preview window on
|
|
# the right (12 hash, 50 subject, 80 patch, 10 git graph and fzf ui).
|
|
#
|
|
# ctrl-space should cycle through the preview-window positions, starting with
|
|
# hidden, then not the start position and than back the start position.
|
|
local -a tty_size
|
|
tty_size=(${=$(command stty size 2>/dev/null)})
|
|
if (( ! $? )) && (( $tty_size[2] > 152 )); then
|
|
fzf_args+=(
|
|
--preview-window=right
|
|
--bind "ctrl-space:change-preview-window(hidden|bottom|right)"
|
|
)
|
|
else
|
|
fzf_args+=(
|
|
--preview-window=down
|
|
--bind "ctrl-space:change-preview-window(hidden|right|bottom)"
|
|
)
|
|
fi
|
|
|
|
# Display the commits in the above format and pipe that into fzf if stdout is a
|
|
# terminal.
|
|
if [ -t 1 ]; then
|
|
git log "$formatshort" "$dateshort" --color=always "$@" \
|
|
| env LESS="$LESS${LESS:+ }-+F" fzf "${fzf_args[@]}"
|
|
else
|
|
git log "$formatshort" "$dateshort" --color=always "$@"
|
|
fi
|
|
return 0
|