wfxr.forgit/tests/working-tree-changes.test.sh
Wenxuan 17110bfc61
fix: preserve add paths across special filenames (#506)
Keep `forgit add` on the porcelain `git status --porcelain -zs` path so
filenames containing backslashes continue to work, while restoring the
behaviors that regressed when we moved away from Git's cwd-relative output.

This change makes the status picker emit a display label and a hidden
absolute-path payload separately. That lets the UI keep showing intuitive
cwd-relative paths, while preview, edit, and add actions operate on the real
path instead of reparsing the rendered status line. As a result, untracked
files, subdirectory workflows, and special filenames now share one consistent
path flow.

It also restores the old-Git fallback for plain `?? path` output before status
filtering, raises the required fzf version for `--accept-nth`, and adds
regression coverage for backslashes, spaces, tabs, subdirectory entries,
sibling directories, and logical symlink paths.

We explored a few alternatives before landing here. Keeping a single
human-readable line and reparsing it downstream remained too fragile for
quoted paths and backslashes. Shell-only display-path rewriting worked for
some cases but stayed brittle across logical vs physical paths and still
failed in macOS CI. A per-path `realpath` approach would have been easier to
read, but GNU-style relative-path support is not portable across the
platforms we test and would add one external process per file in a hot path.

The final tradeoff keeps the pipeline batch-oriented and portable by doing
the path normalization once in a single helper step, even though that is less
lightweight than the earlier shell-only versions. That complexity is
justified here because it fixes the old-Git untracked regression, preserves
correct preview/add behavior from subdirectories, and avoids reintroducing
long-standing filename parsing bugs.
2026-04-08 10:40:03 +08:00

205 lines
5 KiB
Bash

#!/usr/bin/env bash
FORGIT_REPO_ROOT=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
function set_up_before_script() {
source bin/git-forgit
# Ignore global git config files
export GIT_CONFIG_SYSTEM=/dev/null
export GIT_CONFIG_GLOBAL=/dev/null
cd "$(bashunit::temp_dir)" || return 1
git init --quiet
git config user.name "Test User"
git config user.email "test@example.com"
touch "tracked file.txt"
touch "modified file.txt"
git add .
git commit -m "init" --quiet
echo modified > "modified file.txt"
touch "staged file.txt"
git add "staged file.txt"
touch "untracked_file.txt"
touch 'untracked_with_\backslash'
}
function test_forgit_worktree_changes_contains_modified() {
local output
output=$(_forgit_worktree_changes)
assert_contains "modified file.txt" "$output"
}
function test_forgit_worktree_changes_contains_untracked() {
local output
output=$(_forgit_worktree_changes)
assert_contains "untracked_file.txt" "$output"
}
function test_forgit_worktree_changes_excludes_staged() {
local output
output=$(_forgit_worktree_changes)
assert_not_contains "staged file.txt" "$output"
}
function test_forgit_worktree_changes_excludes_tracked() {
local output
output=$(_forgit_worktree_changes)
assert_not_contains "tracked file.txt" "$output"
}
function test_forgit_worktree_changes_supports_backslashes() {
local output
output=$(_forgit_worktree_changes)
assert_contains 'untracked_with_\backslash' "$output"
}
function test_forgit_build_status_entries_uses_cwd_relative_display_paths_within_subdirectories() {
local output
mkdir -p dir
touch dir/file.txt
cd dir || return 1
output=$(_forgit_worktree_changes)
assert_contains "file.txt" "$output"
}
function test_forgit_build_status_entries_prefixes_parent_paths_for_entries_outside_cwd() {
local output
mkdir -p dir other
touch other/file.txt
cd dir || return 1
output=$(_forgit_worktree_changes)
assert_contains "../other/file.txt" "$output"
}
function test_forgit_build_status_entries_keeps_repo_relative_paths_at_repo_root() {
local output
mkdir -p dir
touch dir/file.txt
output=$(_forgit_worktree_changes)
assert_contains "dir/file.txt" "$output"
}
function test_forgit_build_status_entries_uses_cwd_relative_display_paths_for_sibling_entries() {
local output
mkdir -p dir1 dir2
touch dir1/file.txt
cd dir2 || return 1
output=$(_forgit_worktree_changes)
assert_contains "../dir1/file.txt" "$output"
}
function test_forgit_build_status_entries_uses_cwd_relative_display_paths_from_logical_symlink_paths() {
local output sandbox
sandbox=$(bashunit::temp_dir)
mkdir -p "$sandbox/real/dir1" "$sandbox/real/dir2"
(
cd "$sandbox/real" || exit 1
git init --quiet
git config user.name "Test User"
git config user.email "test@example.com"
) || return 1
ln -s "$sandbox/real" "$sandbox/link"
cd "$sandbox/link/dir2" || return 1
touch ../dir1/file.txt
output=$(_forgit_worktree_changes)
assert_contains "../dir1/file.txt" "$output"
}
function test_forgit_worktree_changes_emits_absolute_payloads_for_subdir_entries() {
local output rootdir
mkdir dir
touch 'dir/with_\backslash'
cd dir || return 1
output=$(_forgit_worktree_changes)
rootdir=$(git rev-parse --show-toplevel)
assert_contains $'with_\\backslash'"$_ffsep""$rootdir/dir/with_\\backslash" "$output"
}
function test_forgit_restore_untracked_color_colorizes_plain_untracked_lines() {
local output
output=$(printf '?? plain.txt\n' | _forgit_restore_untracked_color '<u>' '<r>')
assert_same '<u>??<r> plain.txt' "$output"
}
function test_forgit_restore_untracked_color_leaves_colored_lines_unchanged() {
local colored output
colored=$'\033[33m??\033[m plain.txt'
output=$(printf '%s\n' "$colored" | _forgit_restore_untracked_color '<u>' '<r>')
assert_same "$colored" "$output"
}
function test_forgit_worktree_changes_preserves_special_characters_in_payload() {
local output path rootdir
path=$'tab\t space \\ name.txt'
touch "$path"
rootdir=$(git rev-parse --show-toplevel)
output=$(_forgit_worktree_changes)
assert_contains "${path}${_ffsep}${rootdir}/${path}" "$output"
}
function test_forgit_fzf_separator_does_not_use_literal_tabs() {
local delimiter
delimiter=$_ffsep
assert_not_contains $'\t' "$delimiter"
}
function test_forgit_worktree_changes_works_in_zsh() {
local output
output=$(
zsh -c '
source "'"$FORGIT_REPO_ROOT"'/bin/git-forgit"
cd "$(mktemp -d)" || exit 1
git init --quiet
touch "space name.txt" "back\\slash.txt" $'"'"'tab\tname.txt'"'"'
_forgit_worktree_changes
'
)
assert_contains 'space name.txt' "$output"
assert_contains 'back\slash.txt' "$output"
assert_contains 'tab' "$output"
}