From 53c2b3f7169186ecc68f2494196f821ffadf5d0d Mon Sep 17 00:00:00 2001 From: sandroid Date: Tue, 6 Feb 2024 21:55:49 +0100 Subject: [PATCH] Refactor: Replace deferred code used for fzf preview with functions Removes the deferred code that is used for creating the fzf preview functions and replaces it with _forgit_*_preview functions instead. These functions are exposed as forgit commands so they can be invoked from the fzf subshell. We split the exposed commands into public_commands and private_commands. The only difference between them is that public_commands are mentioned in the help text. This commit changes the flags variable in _forgit_blame from a string to an array. This is necessary to allow the flags to be passed to _forgit_blame_preview as individual arguments. --- bin/git-forgit | 287 +++++++++++++++++++++++++++++++++++-------------- 1 file changed, 206 insertions(+), 81 deletions(-) diff --git a/bin/git-forgit b/bin/git-forgit index 00d315b..be5341e 100755 --- a/bin/git-forgit +++ b/bin/git-forgit @@ -70,6 +70,10 @@ hash emojify &>/dev/null && _forgit_emojify='|emojify' # extract the first git sha occurring in the input and strip trailing newline _forgit_extract_sha="grep -Eo '[a-f0-9]+' | head -1 | tr -d '[:space:]'" +_forgit_extract_sha() { + grep -Eo '[a-f0-9]+' | head -1 | tr -d '[:space:]' +} + # parse a space separated string into an array # arrays parsed with this function are global @@ -89,24 +93,33 @@ _forgit_blame_pager=${FORGIT_BLAME_PAGER:-$(git config pager.blame || echo "$_fo _forgit_enter_pager=${FORGIT_ENTER_PAGER:-"LESS='-r' less"} _forgit_log_format=${FORGIT_LOG_FORMAT:-%C(auto)%h%d %s %C(black)%C(bold)%cr%Creset} -_forgit_log_preview_options="--graph --pretty=format:'$_forgit_log_format' --color=always --abbrev-commit --date=relative" +_forgit_log_preview_options=("--graph" "--pretty=format:$_forgit_log_format" "--color=always" "--abbrev-commit" "--date=relative") _forgit_fullscreen_context=${FORGIT_FULLSCREEN_CONTEXT:-10} _forgit_preview_context=${FORGIT_PREVIEW_CONTEXT:-3} -_forgit_is_file_tracked="(git ls-files {} --error-unmatch) &> /dev/null" + +_forgit_is_file_tracked() { + git ls-files "$1" --error-unmatch &> /dev/null +} + +_forgit_log_preview() { + local sha + sha=$(echo "$1" | _forgit_extract_sha) + shift + echo "$sha" | xargs -I% git show --color=always -U"$_forgit_preview_context" % -- "$@" | $_forgit_show_pager +} # git commit viewer _forgit_log() { _forgit_inside_work_tree || return 1 - local opts graph files log_format preview_cmd enter_cmd + local opts graph files log_format enter_cmd files=$(sed -nE 's/.*-- (.*)/\1/p' <<< "$*") # extract files parameters for `git show` command - preview_cmd="echo {} | $_forgit_extract_sha | xargs -I% git show --color=always -U$_forgit_preview_context % -- $files | $_forgit_show_pager" enter_cmd="echo {} | $_forgit_extract_sha | xargs -I% ${FORGIT} diff %^! $files" opts=" $FORGIT_FZF_DEFAULT_OPTS +s +m --tiebreak=index --bind=\"enter:execute($enter_cmd)\" --bind=\"ctrl-y:execute-silent(echo {} | $_forgit_extract_sha | ${FORGIT_COPY_CMD:-pbcopy})\" - --preview=\"$preview_cmd\" + --preview=\"$FORGIT log_preview {} $files\" $FORGIT_LOG_FZF_OPTS " graph=--graph @@ -122,10 +135,45 @@ _forgit_log() { return $fzf_exit_code } +_forgit_get_files_from_diff_line() { + # Construct a null-terminated list of the filenames + # The input looks like one of these lines: + # [R100] file -> another file + # [A] file with spaces + # [D] oldfile + # And we transform it to this representation for further usage with "xargs -0": + # file\0another file\0 + # file with spaces\0 + # oldfile\0 + # We have to do a two-step sed -> tr pipe because OSX's sed implementation does + # not support the null-character directly. + sed 's/.*] *//' | sed 's/ -> /\n/' | tr '\n' '\0' +} + +_forgit_exec_diff() { + _forgit_diff_git_opts=() + _forgit_parse_array _forgit_diff_git_opts "$FORGIT_DIFF_GIT_OPTS" + git diff --color=always "${_forgit_diff_git_opts[@]}" "$@" +} + +_forgit_diff_view() { + local input_line=$1 + local diff_context=$2 + local repo + local commits=() + repo=$(git rev-parse --show-toplevel) + cd "$repo" || return 1 + if [ $# -gt 2 ]; then + IFS=" " read -r -a commits <<< "${*:3}" + fi + echo "$input_line" | _forgit_get_files_from_diff_line | xargs -0 \ + "$FORGIT" exec_diff "${commits[@]}" -U"$diff_context" -- | $_forgit_diff_pager +} + # git diff viewer _forgit_diff() { _forgit_inside_work_tree || return 1 - local files opts commits repo get_files preview_cmd enter_cmd + local files opts commits repo get_files enter_cmd [[ $# -ne 0 ]] && { if git rev-parse "$1" -- &>/dev/null ; then if [[ $# -gt 1 ]] && git rev-parse "$2" -- &>/dev/null; then @@ -161,12 +209,11 @@ _forgit_diff() { _forgit_diff_git_opts=() _forgit_parse_array _forgit_diff_git_opts "$FORGIT_DIFF_GIT_OPTS" git_diff="git diff --color=always ${_forgit_diff_git_opts[*]} $escaped_commits" - preview_cmd="cd '$repo' && $get_files | xargs -0 $git_diff -U$_forgit_preview_context -- | $_forgit_diff_pager" enter_cmd="cd '$repo' && $get_files | xargs -0 $git_diff -U$_forgit_fullscreen_context -- | $_forgit_diff_pager" opts=" $FORGIT_FZF_DEFAULT_OPTS +m -0 --bind=\"enter:execute($enter_cmd | $_forgit_enter_pager)\" - --preview=\"$preview_cmd\" + --preview=\"$FORGIT diff_view {} $_forgit_preview_context $escaped_commits\" --bind=\"alt-e:execute-silent($EDITOR \\\"\$\($get_file)\\\" >/dev/tty \" @@ -180,10 +227,27 @@ _forgit_diff() { return $fzf_exit_code } +_forgit_add_preview() { + file=$(echo "$1" | _forgit_get_single_file_from_add_line) + if (git status -s -- "$file" | grep '^??') &>/dev/null; then # diff with /dev/null for untracked files + git diff --color=always --no-index -- /dev/null "$file" | $_forgit_diff_pager | sed '2 s/added:/untracked:/' + else + git diff --color=always -- "$file" | $_forgit_diff_pager + fi +} + +_forgit_get_single_file_from_add_line() { + # NOTE: paths listed by 'git status -su' mixed with quoted and unquoted style + # remove indicators | remove original path for rename case | remove surrounding quotes + sed 's/^.*] //' | + sed 's/.* -> //' | + sed -e 's/^\"//' -e 's/\"$//' +} + # git add selector _forgit_add() { _forgit_inside_work_tree || return 1 - local git_add changed unmerged untracked files opts preview extract + local git_add changed unmerged untracked files opts extract _forgit_add_git_opts=() _forgit_parse_array _forgit_add_git_opts "$FORGIT_ADD_GIT_OPTS" git_add="git add ${_forgit_add_git_opts[*]}" @@ -199,17 +263,10 @@ _forgit_add() { sed 's/^.*] //' | sed 's/.* -> //' | sed -e 's/^\\\"//' -e 's/\\\"\$//'" - preview=" - file=\$(echo {} | $extract) - if (git status -s -- \\\"\$file\\\" | grep '^??') &>/dev/null; then # diff with /dev/null for untracked files - git diff --color=always --no-index -- /dev/null \\\"\$file\\\" | $_forgit_diff_pager | sed '2 s/added:/untracked:/' - else - git diff --color=always -- \\\"\$file\\\" | $_forgit_diff_pager - fi" opts=" $FORGIT_FZF_DEFAULT_OPTS -0 -m --nth 2..,.. - --preview=\"$preview\" + --preview=\"$FORGIT add_preview {}\" --bind=\"alt-e:execute-silent($EDITOR \\\"\$\(echo {} | $extract\)\\\" >/dev/tty /dev/null +} + _forgit_ignore() { [ -d "$FORGIT_GI_REPO_LOCAL" ] || _forgit_ignore_update - local IFS cmd args opts - cmd="$_forgit_ignore_pager $FORGIT_GI_TEMPLATES/{2}{,.gitignore} 2>/dev/null" + local IFS args opts opts=" $FORGIT_FZF_DEFAULT_OPTS -m --preview-window='right:70%' - --preview=\"eval $cmd\" + --preview=\"$FORGIT ignore_preview {2}\" $FORGIT_IGNORE_FZF_OPTS " ${IFS+"false"} && unset old_IFS || old_IFS="$IFS" @@ -714,7 +820,7 @@ _forgit_ignore_clean() { [[ -d "$FORGIT_GI_REPO_LOCAL" ]] && rm -rf "$FORGIT_GI_REPO_LOCAL" } -valid_commands=( +public_commands=( "add" "blame" "branch_delete" @@ -736,18 +842,37 @@ valid_commands=( "stash_push" ) +private_commands=( + "add_preview" + "blame_preview" + "branch_preview" + "checkout_commit_preview" + "checkout_file_preview" + "cherry_pick_from_branch_preview" + "cherry_pick_preview" + "file_preview" + "ignore_preview" + "revert_preview" + "reset_head_preview" + "stash_push_preview" + "stash_show_preview" + "log_preview" + "exec_diff" + "diff_view" +) + cmd="$1" shift # shellcheck disable=SC2076 -if [[ ! " ${valid_commands[*]} " =~ " ${cmd} " ]]; then +if [[ ! " ${public_commands[*]} " =~ " ${cmd} " ]] && [[ ! " ${private_commands[*]} " =~ " ${cmd} " ]]; then if [[ -z "$cmd" ]]; then printf "forgit: missing command\n\n" else printf "forgit: '%s' is not a valid forgit command.\n\n" "$cmd" fi printf "The following commands are supported:\n" - printf "\t%s\n" "${valid_commands[@]}" + printf "\t%s\n" "${public_commands[@]}" exit 1 fi