Files
dotfiles/.config/zsh/autoload/git/glog
Julian Prein 00d28845a3 zsh:glog: Fix preview with files that were renamed
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
2025-05-23 18:09:02 +02:00

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