From 1e8f79edefe17b35bad55725973ed387e910ce73 Mon Sep 17 00:00:00 2001 From: sandroid Date: Mon, 29 Jan 2024 16:12:32 +0100 Subject: [PATCH] Refactor: Parse environment variables into arrays Forgit allows specifying git options in environment variables that are passed along to the individual git commands. We currently treat those as strings. This commit adds a _forgit_parse_array function and uses it to parse all such environment variables into arrays instead. This will allow us to get rid of deferred code, since we can pass the parsed arrays directly to the git commands and don't have to rely on eval. --- bin/git-forgit | 86 ++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 66 insertions(+), 20 deletions(-) diff --git a/bin/git-forgit b/bin/git-forgit index 0db33a3..55125d6 100755 --- a/bin/git-forgit +++ b/bin/git-forgit @@ -71,6 +71,16 @@ 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:]'" +# parse a space separated string into an array +# arrays parsed with this function are global +_forgit_parse_array() { + ${IFS+"false"} && unset old_IFS || old_IFS="$IFS" + # read the value of the second argument + # into an array that has the name of the first argument + IFS=" " read -r -a "$1" <<< "$2" + ${old_IFS+"false"} && unset IFS || IFS="$old_IFS" +} + _forgit_pager=${FORGIT_PAGER:-$(git config core.pager || echo 'cat')} _forgit_show_pager=${FORGIT_SHOW_PAGER:-$(git config pager.show || echo "$_forgit_pager")} _forgit_diff_pager=${FORGIT_DIFF_PAGER:-$(git config pager.diff || echo "$_forgit_pager")} @@ -102,7 +112,9 @@ _forgit_log() { graph=--graph [[ $FORGIT_LOG_GRAPH_ENABLE == false ]] && graph= log_format=${FORGIT_GLO_FORMAT:-$_forgit_log_format} - eval "git log $graph --color=always --format='$log_format' $FORGIT_LOG_GIT_OPTS $* $_forgit_emojify" | + _forgit_log_git_opts=() + _forgit_parse_array _forgit_log_git_opts "$FORGIT_LOG_GIT_OPTS" + eval "git log $graph --color=always --format='$log_format' ${_forgit_log_git_opts[*]} $* $_forgit_emojify" | FZF_DEFAULT_OPTS="$opts" fzf fzf_exit_code=$? # exit successfully on 130 (ctrl-c/esc) @@ -146,7 +158,9 @@ _forgit_diff() { # prevent fzf from interpreting this substring by escaping the opening bracket. # The string is evaluated a few subsequent times, so we need multiple escapes. escaped_commits=${commits//\{/\\\\\{} - git_diff="git diff --color=always $FORGIT_DIFF_GIT_OPTS $escaped_commits" + _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=" @@ -157,7 +171,7 @@ _forgit_diff() { $FORGIT_DIFF_FZF_OPTS --prompt=\"$commits > \" " - eval "git diff --name-status $FORGIT_DIFF_GIT_OPTS $commits -- ${files[*]} | sed -E 's/^([[:alnum:]]+)[[:space:]]+(.*)$/[\1] \2/'" | + eval "git diff --name-status ${_forgit_diff_git_opts[*]} $commits -- ${files[*]} | sed -E 's/^([[:alnum:]]+)[[:space:]]+(.*)$/[\1] \2/'" | sed 's/ / -> /2' | expand -t 8 | FZF_DEFAULT_OPTS="$opts" fzf fzf_exit_code=$? @@ -170,7 +184,9 @@ _forgit_diff() { _forgit_add() { _forgit_inside_work_tree || return 1 local git_add changed unmerged untracked files opts preview extract - git_add="git add $FORGIT_ADD_GIT_OPTS" + _forgit_add_git_opts=() + _forgit_parse_array _forgit_add_git_opts "$FORGIT_ADD_GIT_OPTS" + git_add="git add ${_forgit_add_git_opts[*]}" # Add files if passed as arguments [[ $# -ne 0 ]] && { $git_add "$@" && git status -su; return $?; } @@ -210,7 +226,9 @@ _forgit_add() { _forgit_reset_head() { _forgit_inside_work_tree || return 1 local git_reset_head cmd files opts rootdir - git_reset_head="git reset -q $FORGIT_RESET_HEAD_GIT_OPTS HEAD" + _forgit_reset_head_git_opts=() + _forgit_parse_array _forgit_reset_head_git_opts "$FORGIT_RESET_HEAD_GIT_OPTS" + git_reset_head="git reset -q ${_forgit_reset_head_git_opts[*]} HEAD" [[ $# -ne 0 ]] && { $git_reset_head "$@" && git status --short; return $?; } rootdir=$(git rev-parse --show-toplevel) cmd="git diff --staged --color=always -- $rootdir/{} | $_forgit_diff_pager " @@ -232,7 +250,9 @@ _forgit_stash_show() { local git_stash_show git_stash_list cmd opts git_stash_show="git stash show --color=always --ext-diff" [[ $# -ne 0 ]] && { $git_stash_show "$@"; return $?; } - git_stash_list="git stash list $FORGIT_STASH_SHOW_GIT_OPTS" + _forgit_stash_show_git_opts=() + _forgit_parse_array _forgit_stash_show_git_opts "$FORGIT_STASH_SHOW_GIT_OPTS" + git_stash_list="git stash list ${_forgit_stash_show_git_opts[*]}" cmd="echo {} |cut -d: -f1 |xargs -I% $git_stash_show % |$_forgit_diff_pager" opts=" $FORGIT_FZF_DEFAULT_OPTS @@ -252,7 +272,9 @@ _forgit_stash_show() { _forgit_stash_push() { _forgit_inside_work_tree || return 1 local git_stash_push msg args - git_stash_push="git stash push $FORGIT_STASH_PUSH_GIT_OPTS" + _forgit_stash_push_git_opts=() + _forgit_parse_array _forgit_stash_push_git_opts "$FORGIT_STASH_PUSH_GIT_OPTS" + git_stash_push="git stash push ${_forgit_stash_push_git_opts[*]}" args=( "$@" ) while (( "$#" )); do case "$1" in @@ -291,7 +313,9 @@ _forgit_clean() { _forgit_inside_work_tree || return 1 _forgit_contains_non_flags "$@" && { git clean -q "$@"; return $?; } local git_clean files opts - git_clean="git clean $FORGIT_CLEAN_GIT_OPTS" + _forgit_clean_git_opts=() + _forgit_parse_array _forgit_clean_git_opts "$FORGIT_CLEAN_GIT_OPTS" + git_clean="git clean ${_forgit_clean_git_opts[*]}" opts=" $FORGIT_FZF_DEFAULT_OPTS -m -0 @@ -307,7 +331,9 @@ _forgit_clean() { _forgit_cherry_pick() { local git_cherry_pick base target preview opts fzf_selection fzf_exitval - git_cherry_pick="git cherry-pick $FORGIT_CHERRY_PICK_GIT_OPTS" + _forgit_cherry_pick_git_opts=() + _forgit_parse_array _forgit_cherry_pick_git_opts "$FORGIT_CHERRY_PICK_GIT_OPTS" + git_cherry_pick="git cherry-pick ${_forgit_cherry_pick_git_opts[*]}" base=$(git branch --show-current) [[ -z "$base" ]] && echo "Current commit is not on a branch." && return 1 @@ -387,7 +413,9 @@ _forgit_cherry_pick_from_branch() { _forgit_rebase() { _forgit_inside_work_tree || return 1 local git_rebase cmd preview opts graph files target_commit prev_commit - git_rebase="git rebase -i $FORGIT_REBASE_GIT_OPTS" + _forgit_rebase_git_opts=() + _forgit_parse_array _forgit_rebase_git_opts "$FORGIT_REBASE_GIT_OPTS" + git_rebase="git rebase -i ${_forgit_rebase_git_opts[*]}" graph=--graph [[ $FORGIT_LOG_GRAPH_ENABLE == false ]] && graph= cmd="git log $graph --color=always --format='$_forgit_log_format' $* $_forgit_emojify" @@ -412,7 +440,9 @@ _forgit_fixup() { _forgit_inside_work_tree || return 1 git diff --cached --quiet && echo 'Nothing to fixup: there are no staged changes.' && return 1 local git_fixup cmd preview opts graph files target_commit prev_commit - git_fixup="git commit --fixup $FORGIT_FIXUP_GIT_OPTS" + _forgit_fixup_git_opts=() + _forgit_parse_array _forgit_fixup_git_opts "$FORGIT_FIXUP_GIT_OPTS" + git_fixup="git commit --fixup ${_forgit_fixup_git_opts[*]}" graph=--graph [[ $FORGIT_LOG_GRAPH_ENABLE == false ]] && graph= cmd="git log $graph --color=always --format='$_forgit_log_format' $* $_forgit_emojify" @@ -439,7 +469,9 @@ _forgit_fixup() { _forgit_checkout_file() { _forgit_inside_work_tree || return 1 local git_checkout cmd files opts - git_checkout="git checkout $FORGIT_CHECKOUT_FILE_GIT_OPTS" + _forgit_checkout_file_git_opts=() + _forgit_parse_array _forgit_checkout_file_git_opts "$FORGIT_CHECKOUT_FILE_GIT_OPTS" + git_checkout="git checkout ${_forgit_checkout_file_git_opts[*]}" [[ $# -ne 0 ]] && { $git_checkout -- "$@"; return $?; } cmd="git diff --color=always -- {} | $_forgit_diff_pager" opts=" @@ -468,7 +500,9 @@ _forgit_checkout_branch() { fi local git_checkout cmd preview opts branch - cmd="git branch --color=always ${FORGIT_CHECKOUT_BRANCH_BRANCH_GIT_OPTS:---all} | LC_ALL=C sort -k1.1,1.1 -rs" + _forgit_checkout_branch_branch_git_opts=() + _forgit_parse_array _forgit_checkout_branch_branch_git_opts "$FORGIT_CHECKOUT_BRANCH_BRANCH_GIT_OPTS" + cmd="git branch --color=always ${_forgit_checkout_branch_branch_git_opts[*]:---all} | LC_ALL=C sort -k1.1,1.1 -rs" preview="git log {1} $_forgit_log_preview_options" opts=" $FORGIT_FZF_DEFAULT_OPTS @@ -479,7 +513,9 @@ _forgit_checkout_branch() { branch="$(eval "$cmd" | FZF_DEFAULT_OPTS="$opts" fzf | awk '{print $1}')" [[ -z "$branch" ]] && return 1 - git_checkout="git checkout $FORGIT_CHECKOUT_BRANCH_GIT_OPTS" + _forgit_checkout_branch_git_opts=() + _forgit_parse_array _forgit_checkout_branch_git_opts "$FORGIT_CHECKOUT_BRANCH_GIT_OPTS" + git_checkout="git checkout ${_forgit_checkout_branch_git_opts[*]}" # track the remote branch if possible if [[ "$branch" == "remotes/origin/"* ]]; then if git branch | grep -qw "${branch#remotes/origin/}"; then @@ -497,7 +533,9 @@ _forgit_checkout_branch() { _forgit_checkout_tag() { _forgit_inside_work_tree || return 1 local git_checkout cmd opts preview - git_checkout="git checkout $FORGIT_CHECKOUT_TAG_GIT_OPTS" + _forgit_checkout_tag_git_opts=() + _forgit_parse_array _forgit_checkout_tag_git_opts "$FORGIT_CHECKOUT_TAG_GIT_OPTS" + git_checkout="git checkout ${_forgit_checkout_tag_git_opts[*]}" [[ $# -ne 0 ]] && { $git_checkout "$@"; return $?; } cmd="git tag -l --sort=-v:refname" preview="git log {1} $_forgit_log_preview_options" @@ -516,7 +554,9 @@ _forgit_checkout_tag() { _forgit_checkout_commit() { _forgit_inside_work_tree || return 1 local git_checkout cmd opts graph - git_checkout="git checkout $FORGIT_CHECKOUT_COMMIT_GIT_OPTS" + _forgit_checkout_commit_git_opts=() + _forgit_parse_array _forgit_checkout_commit_git_opts "$FORGIT_CHECKOUT_COMMIT_GIT_OPTS" + git_checkout="git checkout ${_forgit_checkout_commit_git_opts[*]}" [[ $# -ne 0 ]] && { $git_checkout "$@"; return $?; } cmd="echo {} | $_forgit_extract_sha |xargs -I% git show --color=always % | $_forgit_show_pager" opts=" @@ -536,7 +576,9 @@ _forgit_checkout_commit() { _forgit_branch_delete() { _forgit_inside_work_tree || return 1 local git_branch preview opts cmd branches - git_branch="git branch $FORGIT_BRANCH_DELETE_GIT_OPTS" + _forgit_branch_delete_git_opts=() + _forgit_parse_array _forgit_branch_delete_git_opts "$FORGIT_BRANCH_DELETE_GIT_OPTS" + git_branch="git branch ${_forgit_branch_delete_git_opts[*]}" [[ $# -ne 0 ]] && { $git_branch -D "$@"; return $?; } preview="git log {1} $_forgit_log_preview_options" @@ -557,7 +599,9 @@ _forgit_branch_delete() { _forgit_revert_commit() { _forgit_inside_work_tree || return 1 local git_revert cmd opts files preview commits IFS - git_revert="git revert $FORGIT_REVERT_COMMIT_GIT_OPTS" + _forgit_revert_commit_git_opts=() + _forgit_parse_array _forgit_revert_commit_git_opts "$FORGIT_REVERT_COMMIT_GIT_OPTS" + git_revert="git revert ${_forgit_revert_commit_git_opts[*]}" [[ $# -ne 0 ]] && { $git_revert "$@"; return $?; } cmd="git log --graph --color=always --format='$_forgit_log_format' $* $_forgit_emojify" @@ -596,7 +640,9 @@ _forgit_revert_commit() { _forgit_blame() { _forgit_inside_work_tree || return 1 local git_blame opts flags preview file - git_blame="git blame $FORGIT_BLAME_GIT_OPTS" + _forgit_blame_git_opts=() + _forgit_parse_array _forgit_blame_git_opts "$FORGIT_BLAME_GIT_OPTS" + git_blame="git blame ${_forgit_blame_git_opts[*]}" _forgit_contains_non_flags "$@" && { $git_blame "$@"; return $?; } opts=" $FORGIT_FZF_DEFAULT_OPTS @@ -605,7 +651,7 @@ _forgit_blame() { flags=$(git rev-parse --flags "$@") preview=" if $_forgit_is_file_tracked; then - git blame {} --date=short $FORGIT_BLAME_GIT_OPTS $flags | $_forgit_blame_pager + git blame {} --date=short ${_forgit_blame_git_opts[*]} $flags | $_forgit_blame_pager else echo File not tracked fi