Skip to content

Commit

Permalink
Fix: file commands do not work with paths containing special characters
Browse files Browse the repository at this point in the history
Git escapes special characters in it's output when core.quotePath is
true or unset. Git always expects unquoted file paths as input. This
leads to issues when we consume output from git and use it to build
input for other git commands. This commit ensures we always feed unqoted
paths to git commands.
The _forgit_list_files function is introduced to handle usage of git
ls-files with the -z flag, which ensures unquoted paths.
It replaces the direct calls to git ls-files in _forgit_reset_head,
_forgit_stash_push and _forgit_checkout_file.
In _forgit_add the current approach of using colors to separate unstaged
from staged files is replaced with a call to _forgit_ls_files with the
appropriate flags to only list unstaged files.
In _git_reset_head the -z option is added to the git diff command to
ensure unqoted paths.
Since git clean does not support the -z flag, we disable core.quotePath
by passing a configuration parameter in _forgit_clean.
  • Loading branch information
sandr01d committed Apr 23, 2024
1 parent e69d21a commit b9f3055
Showing 1 changed file with 22 additions and 12 deletions.
34 changes: 22 additions & 12 deletions bin/git-forgit
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,21 @@ _forgit_is_file_tracked() {
git ls-files "$1" --error-unmatch &> /dev/null
}

_forgit_list_files() {
local rootdir
rootdir=$(git rev-parse --show-toplevel)
# git escapes special characters in it's output when core.quotePath is
# true or unset. Git always expects unquoted file paths as input. This
# leads to issues when we consume output from git and use it to build
# input for other git commands. Use the -z flag to ensure file paths are
# unquoted.
# uniq is necessary because unmerged files are printed once for each
# merge conflict.
# With the -z flag, git also uses \0 line termination, so we
# have to replace the terminators.
git ls-files -z "$@" "$rootdir" | uniq | tr '\0' '\n'
}

_forgit_log_preview() {
local sha
sha=$(echo "$1" | _forgit_extract_sha)
Expand Down Expand Up @@ -329,13 +344,10 @@ _forgit_edit_add_file() {
# git add selector
_forgit_add() {
_forgit_inside_work_tree || return 1
local changed unmerged untracked files opts
local files opts
# Add files if passed as arguments
[[ $# -ne 0 ]] && { _forgit_git_add "$@" && git status -su; return $?; }

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)
opts="
$FORGIT_FZF_DEFAULT_OPTS
-0 -m --nth 2..,..
Expand All @@ -346,8 +358,7 @@ _forgit_add() {
files=()
while IFS='' read -r file; do
files+=("$file")
done < <(git -c color.status=always -c status.relativePaths=true status -su |
grep -F -e "$changed" -e "$unmerged" -e "$untracked" |
done < <(_forgit_list_files --exclude-standard --modified --others |
sed -E 's/^(..[^[:space:]]*)[[:space:]]+(.*)$/[\1] \2/' |
FZF_DEFAULT_OPTS="$opts" fzf |
_forgit_get_single_file_from_add_line)
Expand Down Expand Up @@ -381,7 +392,7 @@ _forgit_reset_head() {
files=()
while IFS='' read -r file; do
files+=("$file")
done < <(git diff --staged --name-only | FZF_DEFAULT_OPTS="$opts" fzf)
done < <(git diff -z --staged --name-only | tr '\0' '\n' | FZF_DEFAULT_OPTS="$opts" fzf)
if [[ ${#files} -eq 0 ]]; then
echo 'Nothing to unstage.'
return 1
Expand Down Expand Up @@ -455,19 +466,18 @@ _forgit_stash_push() {
*) _forgit_git_stash_push "${args[@]}"; return $?
esac
done
local opts files rootdir
local opts files
opts="
$FORGIT_FZF_DEFAULT_OPTS
-m
--preview=\"$FORGIT stash_push_preview {}\"
$FORGIT_STASH_PUSH_FZF_OPTS
"
rootdir=$(git rev-parse --show-toplevel)
# Show both modified and untracked files
files=()
while IFS='' read -r file; do
files+=("$file")
done < <(git ls-files "$rootdir" --exclude-standard --modified --others |
done < <(_forgit_list_files --exclude-standard --modified --others |
FZF_DEFAULT_OPTS="$opts" fzf --exit-0)
[[ "${#files[@]}" -eq 0 ]] && echo "Nothing to stash" && return 1
_forgit_git_stash_push ${msg:+-m "$msg"} -u "${files[@]}"
Expand Down Expand Up @@ -497,7 +507,7 @@ _forgit_clean() {
$FORGIT_CLEAN_FZF_OPTS
"
# 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#/$##')
files=$(git -c core.quotePath=false 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 "${_forgit_clean_git_opts[@]}" -xdff '%' && git status --short && return
echo 'Nothing to clean.'
}
Expand Down Expand Up @@ -675,7 +685,7 @@ _forgit_checkout_file() {
files=()
while IFS='' read -r file; do
files+=("$file")
done < <(git ls-files --modified "$(git rev-parse --show-toplevel)" |
done < <(_forgit_list_files --modified |
FZF_DEFAULT_OPTS="$opts" fzf)
[[ "${#files[@]}" -gt 0 ]] && _forgit_git_checkout_file "${files[@]}"
}
Expand Down

0 comments on commit b9f3055

Please sign in to comment.