191 lines
6.6 KiB
Bash
Executable File
191 lines
6.6 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="$(git config --get --default="" core.pager)"
|
|
local into_pager="${pager:+|} $pager"
|
|
|
|
# NOTE: use read for a bit better readability of the code since less needs
|
|
# quoting and formatting is easier
|
|
local get_hash_cmd show_cmd
|
|
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
|
|
local 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-silent@$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 color_brown=$'\e[38;5;144m'
|
|
local color_grey=$'\e[38;5;59m'
|
|
local header=""
|
|
header+="${color_brown}Enter${color_grey}: fullscreen, "
|
|
header+="${color_brown}C-y${color_grey}: yank hash, "
|
|
header+="${color_brown}C-Space${color_grey}: cycle preview, "
|
|
header+="${color_brown}C-[spf]${color_grey}: preview stats/patch/files"
|
|
|
|
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" "$header"
|
|
# 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
|