Fix git options (#296)

Adding options to a command in a quoted variable as introduced in #292
causes problems in several cases unfortunately (e.g. git rebase
interpreting an empty string argument as a remote, which leads to an
error "unknown upstream ''").

Change the implementation in a way that each git command is stored in a
shell variable, including the options, which is then evaluated.
This also makes the code more DRY because each git options variable is
evaluated only once.

Furthermore, fix temporary IFS settings. When setting the IFS for a
certain command only, the command must not be an assignment, otherwise
both assignments are evaluated permanently
(see https://unix.stackexchange.com/a/458901/317320).
This commit is contained in:
Tim 2023-03-18 16:18:11 +01:00 committed by GitHub
parent 2a2ee36d56
commit 450615d48f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -153,10 +153,11 @@ _forgit_diff() {
# git add selector
_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"
# Add files if passed as arguments
[[ $# -ne 0 ]] && git add "$FORGIT_ADD_GIT_OPTS" "$@" && git status -su && return
[[ $# -ne 0 ]] && $git_add "$@" && git status -su && return
local changed unmerged untracked files opts preview extract
changed=$(git config --get-color color.status.changed red)
unmerged=$(git config --get-color color.status.unmerged red)
untracked=$(git config --get-color color.status.untracked red)
@ -184,14 +185,15 @@ _forgit_add() {
sed -E 's/^(..[^[:space:]]*)[[:space:]]+(.*)$/[\1] \2/' |
FZF_DEFAULT_OPTS="$opts" fzf |
sh -c "$extract")
[[ -n "$files" ]] && echo "$files"| tr '\n' '\0' | git add --pathspec-file-nul --pathspec-from-file "$FORGIT_ADD_GIT_OPTS" - && git status -su && return
[[ -n "$files" ]] && echo "$files"| tr '\n' '\0' | $git_add --pathspec-file-nul --pathspec-from-file - && git status -su && return
echo 'Nothing to add.'
}
# git reset HEAD (unstage) selector
_forgit_reset_head() {
_forgit_inside_work_tree || return 1
local cmd files opts rootdir
local git_reset_head cmd files opts rootdir
git_reset_head="git reset -q $FORGIT_RESET_HEAD_GIT_OPTS HEAD"
rootdir=$(git rev-parse --show-toplevel)
cmd="git diff --staged --color=always -- $rootdir/{} | $_forgit_diff_pager "
opts="
@ -201,22 +203,24 @@ _forgit_reset_head() {
$FORGIT_RESET_HEAD_FZF_OPTS
"
files="$(git diff --staged --name-only | FZF_DEFAULT_OPTS="$opts" fzf)"
[[ -n "$files" ]] && echo "$files" | tr '\n' '\0' | xargs -0 -I% git reset -q "$FORGIT_RESET_HEAD_GIT_OPTS" HEAD "$rootdir"/% && git status --short && return
# shellcheck disable=2086
[[ -n "$files" ]] && echo "$files" | tr '\n' '\0' | xargs -0 -I% $git_reset_head "$rootdir"/% && git status --short && return
echo 'Nothing to unstage.'
}
# git stash viewer
_forgit_stash_show() {
_forgit_inside_work_tree || return 1
local cmd opts
cmd="echo {} |cut -d: -f1 |xargs -I% git stash show --color=always --ext-diff $FORGIT_STASH_SHOW_GIT_OPTS % |$_forgit_diff_pager"
local git_stash_list cmd opts
git_stash_list="git stash list $FORGIT_STASH_SHOW_GIT_OPTS"
cmd="echo {} |cut -d: -f1 |xargs -I% git stash show --color=always --ext-diff % |$_forgit_diff_pager"
opts="
$FORGIT_FZF_DEFAULT_OPTS
+s +m -0 --tiebreak=index --bind=\"enter:execute($cmd | $_forgit_enter_pager)\"
--preview=\"$cmd\"
$FORGIT_STASH_FZF_OPTS
"
git stash list | FZF_DEFAULT_OPTS="$opts" fzf
$git_stash_list | FZF_DEFAULT_OPTS="$opts" fzf
fzf_exit_code=$?
# exit successfully on 130 (ctrl-c/esc)
[[ $fzf_exit_code == 130 ]] && return 0
@ -226,7 +230,8 @@ _forgit_stash_show() {
# git stash push selector
_forgit_stash_push() {
_forgit_inside_work_tree || return 1
local msg args
local git_stash_push msg args
git_stash_push="git stash push $FORGIT_STASH_PUSH_GIT_OPTS"
args=( "$@" )
while (( "$#" )); do
case "$1" in
@ -238,7 +243,7 @@ _forgit_stash_push() {
# ignore -u as it's used implicitly
-u|--include-untracked) shift ;;
# pass to git directly when encountering anything else
*) git stash push "$FORGIT_STASH_PUSH_GIT_OPTS" "${args[@]}" && return $?
*) $git_stash_push "${args[@]}" && return $?
esac
done
local opts preview files
@ -257,13 +262,14 @@ _forgit_stash_push() {
# Show both modified and untracked files
files=$(git ls-files --exclude-standard --modified --others | FZF_DEFAULT_OPTS="$opts" fzf --preview="$preview")
[[ -z "$files" ]] && return 1
echo "${files[@]}" | tr '\n' '\0' | git stash push ${msg:+-m "$msg"} -u --pathspec-file-nul --pathspec-from-file "$FORGIT_STASH_PUSH_GIT_OPTS" -
echo "${files[@]}" | tr '\n' '\0' | $git_stash_push ${msg:+-m "$msg"} -u --pathspec-file-nul --pathspec-from-file -
}
# git clean selector
_forgit_clean() {
_forgit_inside_work_tree || return 1
local files opts
local git_clean files opts
git_clean="git clean $FORGIT_CLEAN_GIT_OPTS"
opts="
$FORGIT_FZF_DEFAULT_OPTS
-m -0
@ -271,12 +277,15 @@ _forgit_clean() {
"
# Note: Postfix '/' in directory path should be removed. Otherwise the directory itself will not be removed.
files=$(git clean -xdffn "$@"| sed 's/^Would remove //' | FZF_DEFAULT_OPTS="$opts" fzf |sed 's#/$##')
[[ -n "$files" ]] && echo "$files" | tr '\n' '\0' | xargs -0 -I% git clean -xdff "$FORGIT_CLEAN_GIT_OPTS" '%' && git status --short && return
# shellcheck disable=2086
[[ -n "$files" ]] && echo "$files" | tr '\n' '\0' | xargs -0 -I% $git_clean -xdff '%' && git status --short && return
echo 'Nothing to clean.'
}
_forgit_cherry_pick() {
local base target preview opts fzf_selection fzf_exitval
local git_cherry_pick base target preview opts fzf_selection fzf_exitval
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
@ -308,7 +317,7 @@ _forgit_cherry_pick() {
IFS=$'\n' commits=($(echo "$fzf_selection" | sort --numeric-sort --key=1 | cut -f2 | cut -d' ' -f1 | _forgit_reverse_lines))
[ ${#commits[@]} -eq 0 ] && return 1
git cherry-pick "$FORGIT_CHERRY_PICK_GIT_OPTS" "${commits[@]}"
$git_cherry_pick "${commits[@]}"
}
_forgit_cherry_pick_from_branch() {
@ -352,7 +361,8 @@ _forgit_cherry_pick_from_branch() {
_forgit_rebase() {
_forgit_inside_work_tree || return 1
local cmd preview opts graph files target_commit prev_commit
local git_rebase cmd preview opts graph files target_commit prev_commit
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"
@ -369,14 +379,15 @@ _forgit_rebase() {
if [[ -n "$target_commit" ]]; then
prev_commit=$(_forgit_previous_commit "$target_commit")
git rebase -i "$FORGIT_REBASE_GIT_OPTS" "$prev_commit"
$git_rebase "$prev_commit"
fi
}
_forgit_fixup() {
_forgit_inside_work_tree || return 1
git diff --cached --quiet && echo 'Nothing to fixup: there are no staged changes.' && return 1
local cmd preview opts graph files target_commit prev_commit
local git_fixup cmd preview opts graph files target_commit prev_commit
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"
@ -390,11 +401,11 @@ _forgit_fixup() {
$FORGIT_FIXUP_FZF_OPTS
"
target_commit=$(eval "$cmd" | FZF_DEFAULT_OPTS="$opts" fzf | eval "$_forgit_extract_sha")
if [[ -n "$target_commit" ]] && git commit --fixup "$target_commit"; then
if [[ -n "$target_commit" ]] && $git_fixup "$target_commit"; then
prev_commit=$(_forgit_previous_commit "$target_commit")
# rebase will fail if there are unstaged changes so --autostash is needed to temporarily stash them
# GIT_SEQUENCE_EDITOR=: is needed to skip the editor
GIT_SEQUENCE_EDITOR=: git rebase --autostash -i --autosquash "$FORGIT_FIXUP_GIT_OPTS" "$prev_commit"
GIT_SEQUENCE_EDITOR=: git rebase --autostash -i --autosquash "$prev_commit"
fi
}
@ -402,8 +413,9 @@ _forgit_fixup() {
# git checkout-file selector
_forgit_checkout_file() {
_forgit_inside_work_tree || return 1
[[ $# -ne 0 ]] && { git checkout "$FORGIT_CHECKOUT_FILE_GIT_OPTS" -- "$@"; return $?; }
local cmd files opts
local git_checkout cmd files opts
git_checkout="git checkout $FORGIT_CHECKOUT_FILE_GIT_OPTS"
[[ $# -ne 0 ]] && { $git_checkout -- "$@"; return $?; }
cmd="git diff --color=always -- {} | $_forgit_diff_pager"
opts="
$FORGIT_FZF_DEFAULT_OPTS
@ -412,7 +424,7 @@ _forgit_checkout_file() {
$FORGIT_CHECKOUT_FILE_FZF_OPTS
"
files="$(git ls-files --modified "$(git rev-parse --show-toplevel)"| FZF_DEFAULT_OPTS="$opts" fzf)"
[[ -n "$files" ]] && echo "$files" | tr '\n' '\0' | git checkout --pathspec-file-nul --pathspec-from-file "$FORGIT_CHECKOUT_FILE_GIT_OPTS" -
[[ -n "$files" ]] && echo "$files" | tr '\n' '\0' | $git_checkout --pathspec-file-nul --pathspec-from-file -
}
# git checkout-branch selector
@ -430,7 +442,7 @@ _forgit_checkout_branch() {
return $checkout_status
fi
local cmd preview opts branch
local git_checkout cmd preview opts branch
cmd="git branch --color=always --all | LC_ALL=C sort -k1.1,1.1 -rs"
preview="git log {1} $_forgit_log_preview_options"
opts="
@ -459,8 +471,9 @@ _forgit_checkout_branch() {
# git checkout-tag selector
_forgit_checkout_tag() {
_forgit_inside_work_tree || return 1
[[ $# -ne 0 ]] && { git checkout "$FORGIT_CHECKOUT_TAG_GIT_OPTS" "$@"; return $?; }
local cmd opts preview
local git_checkout cmd opts preview
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"
opts="
@ -471,14 +484,15 @@ _forgit_checkout_tag() {
"
tag="$(eval "$cmd" | FZF_DEFAULT_OPTS="$opts" fzf)"
[[ -z "$tag" ]] && return 1
git checkout "$FORGIT_CHECKOUT_TAG_GIT_OPTS" "$tag"
$git_checkout "$tag"
}
# git checkout-commit selector
_forgit_checkout_commit() {
_forgit_inside_work_tree || return 1
[[ $# -ne 0 ]] && { git checkout "$FORGIT_CHECKOUT_COMMIT_GIT_OPTS" "$@"; return $?; }
local cmd opts graph
local git_checkout cmd opts graph
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="
$FORGIT_FZF_DEFAULT_OPTS
@ -489,13 +503,15 @@ _forgit_checkout_commit() {
"
graph=--graph
[[ $FORGIT_LOG_GRAPH_ENABLE == false ]] && graph=
eval "git log $graph --color=always --format='$_forgit_log_format' $FORGIT_CHECKOUT_COMMIT_GIT_OPTS $_forgit_emojify" |
FZF_DEFAULT_OPTS="$opts" fzf | eval "$_forgit_extract_sha" | xargs -I% git checkout % --
# shellcheck disable=2086
eval "git log $graph --color=always --format='$_forgit_log_format' $_forgit_emojify" |
FZF_DEFAULT_OPTS="$opts" fzf | eval "$_forgit_extract_sha" | xargs -I% $git_checkout % --
}
_forgit_branch_delete() {
_forgit_inside_work_tree || return 1
local preview opts cmd branches
local git_branch preview opts cmd branches
git_branch="git branch $FORGIT_BRANCH_DELETE_GIT_OPTS"
preview="git log {1} $_forgit_log_preview_options"
opts="
@ -507,15 +523,17 @@ _forgit_branch_delete() {
cmd="git branch --color=always | LC_ALL=C sort -k1.1,1.1 -rs"
branches=$(eval "$cmd" | FZF_DEFAULT_OPTS="$opts" fzf | awk '{print $1}')
echo -n "$branches" | tr '\n' '\0' | xargs -I{} -0 git branch "$FORGIT_BRANCH_DELETE_GIT_OPTS" -D {}
# shellcheck disable=2086
echo -n "$branches" | tr '\n' '\0' | xargs -I{} -0 $git_branch -D {}
}
# git revert-commit selector
_forgit_revert_commit() {
_forgit_inside_work_tree || return 1
[[ $# -ne 0 ]] && { git revert "$FORGIT_REVERT_COMMIT_GIT_OPTS" "$@"; return $?; }
local git_revert cmd opts files preview commits IFS
git_revert="git branch $FORGIT_REVERT_COMMIT_GIT_OPTS"
[[ $# -ne 0 ]] && { $git_revert "$@"; return $?; }
local cmd opts files preview commits IFS
cmd="git log --graph --color=always --format='$_forgit_log_format' $* $_forgit_emojify"
opts="
$FORGIT_FZF_DEFAULT_OPTS
@ -542,14 +560,15 @@ _forgit_revert_commit() {
[ ${#commits[@]} -eq 0 ] && return 1
git revert "$FORGIT_REVERT_COMMIT_GIT_OPTS" "${commits[@]}"
$git_revert "${commits[@]}"
}
# git blame viewer
_forgit_blame() {
_forgit_inside_work_tree || return 1
[[ $# -ne 0 ]] && git blame "$FORGIT_BLAME_GIT_OPTS" "$@" && return 0
local opts flags preview file
local git_blame opts flags preview file
git_blame="git blame $FORGIT_BLAME_GIT_OPTS"
[[ $# -ne 0 ]] && $git_blame "$@" && return 0
opts="
$FORGIT_FZF_DEFAULT_OPTS
$FORGIT_BLAME_FZF_OPTS
@ -564,7 +583,8 @@ _forgit_blame() {
"
file=$(FZF_DEFAULT_OPTS="$opts" fzf --preview="$preview")
[[ -z "$file" ]] && return 1
eval git blame "$FORGIT_BLAME_GIT_OPTS" "$file" "$flags"
# shellcheck disable=2086
eval $git_blame "$file" "$flags"
}
# git ignore generator