mirror of
https://github.com/wfxr/forgit.git
synced 2026-09-10 07:16:23 -04:00
perf: speed up modified file lookup in large repositories (#544)
Use a modified-file-only path for `git-forgit restore` (`grs`) and `git-forgit checkout_file` (`gcf`) so these interactive selectors do not scan large sets of untracked files. This keeps both commands responsive in repositories with many untracked files.
This commit is contained in:
parent
e0c7b844b5
commit
a8e9d8bf57
|
|
@ -222,6 +222,61 @@ _forgit_list_files() {
|
|||
git ls-files -z "$@" "$rootdir" | tr '\0' '\n' | uniq
|
||||
}
|
||||
|
||||
_forgit_rewrite_repo_paths_for_cwd() {
|
||||
local rootdir mode separator cwd
|
||||
rootdir=$1
|
||||
mode=${2:-paths}
|
||||
separator=${3:-}
|
||||
cwd=$(pwd -P)
|
||||
|
||||
perl -MCwd=realpath -MFile::Spec -e '
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
my ($rootdir, $cwd, $mode, $separator) = @ARGV;
|
||||
my $normalized_rootdir = realpath($rootdir);
|
||||
my $normalized_cwd = realpath($cwd);
|
||||
|
||||
while (my $line = <STDIN>) {
|
||||
chomp $line;
|
||||
|
||||
my ($status, $repo_path);
|
||||
if ($mode eq q{status_entries}) {
|
||||
next unless $line =~ /^(..[^[:space:]]*)( )(.*)$/;
|
||||
($status, $repo_path) = ($1, $3);
|
||||
} else {
|
||||
next if $line eq q{};
|
||||
$repo_path = $line;
|
||||
}
|
||||
|
||||
my $absolute_path = "$normalized_rootdir/$repo_path";
|
||||
my $display_path = File::Spec->abs2rel(realpath($absolute_path) // $absolute_path, $normalized_cwd);
|
||||
$display_path = q{.} if $display_path eq q{};
|
||||
|
||||
if ($mode eq q{status_entries}) {
|
||||
print "[$status] ${display_path}${separator}${normalized_rootdir}/${repo_path}\n";
|
||||
} else {
|
||||
print "$display_path\n";
|
||||
}
|
||||
}
|
||||
' "$rootdir" "$cwd" "$mode" "$separator"
|
||||
}
|
||||
|
||||
_forgit_list_modified_files() {
|
||||
# on large repos with many untracked files, git ls-files
|
||||
# is substantially slower than git diff
|
||||
# Wherever possible (when we don't care about untracked files)
|
||||
# use this command over _forgit_list_files
|
||||
|
||||
# See note on _forgit_list_files() about -z and other options
|
||||
local rootdir
|
||||
rootdir=$(git rev-parse --show-toplevel)
|
||||
git -C "$rootdir" diff --name-only -z |
|
||||
tr '\0' '\n' |
|
||||
uniq |
|
||||
_forgit_rewrite_repo_paths_for_cwd "$rootdir"
|
||||
}
|
||||
|
||||
_forgit_list_staged_files() {
|
||||
local up
|
||||
up="$(git rev-parse --show-cdup)"
|
||||
|
|
@ -291,32 +346,12 @@ _forgit_restore_untracked_color() {
|
|||
# 1. the human-readable status line shown in fzf
|
||||
# 2. the absolute-path payload used by preview/edit/add actions
|
||||
_forgit_build_status_entries() {
|
||||
local cwd rootdir
|
||||
local rootdir
|
||||
rootdir=$1
|
||||
cwd=$(pwd -P)
|
||||
|
||||
# Use a single Perl process so path normalization stays portable while the
|
||||
# full add-list transformation still runs as one batch pipeline stage.
|
||||
perl -MCwd=realpath -MFile::Spec -e '
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
my ($rootdir, $cwd, $separator) = @ARGV;
|
||||
my $normalized_rootdir = realpath($rootdir);
|
||||
my $normalized_cwd = realpath($cwd);
|
||||
|
||||
while (my $line = <STDIN>) {
|
||||
chomp $line;
|
||||
next unless $line =~ /^(..[^[:space:]]*)( )(.*)$/;
|
||||
|
||||
my ($status, $repo_path) = ($1, $3);
|
||||
my $absolute_path = "$normalized_rootdir/$repo_path";
|
||||
my $display_path = File::Spec->abs2rel(realpath($absolute_path) // $absolute_path, $normalized_cwd);
|
||||
$display_path = "." if $display_path eq q{};
|
||||
|
||||
print "[$status] ${display_path}${separator}${normalized_rootdir}/${repo_path}\n";
|
||||
}
|
||||
' "$rootdir" "$cwd" "$_ffsep"
|
||||
_forgit_rewrite_repo_paths_for_cwd "$rootdir" status_entries "$_ffsep"
|
||||
}
|
||||
|
||||
_forgit_is_submodule() {
|
||||
|
|
@ -739,7 +774,7 @@ _forgit_restore() {
|
|||
if [[ $worktree == true || $staged != true ]]; then
|
||||
while IFS='' read -r file; do
|
||||
candidates+=("$file")
|
||||
done < <(_forgit_list_files --modified)
|
||||
done < <(_forgit_list_modified_files)
|
||||
fi
|
||||
|
||||
[[ ${#candidates[@]} -eq 0 ]] && echo "Nothing to restore." && return 1
|
||||
|
|
@ -1093,7 +1128,7 @@ _forgit_checkout_file() {
|
|||
_forgit_git_checkout_file "$@"
|
||||
return $?
|
||||
}
|
||||
[[ $(_forgit_list_files --modified | wc -l) -eq 0 ]] && echo 'Nothing to checkout.' && return 1
|
||||
[[ $(_forgit_list_modified_files | wc -l) -eq 0 ]] && echo 'Nothing to checkout.' && return 1
|
||||
opts="
|
||||
$FORGIT_FZF_DEFAULT_OPTS
|
||||
-m -0
|
||||
|
|
@ -1103,7 +1138,7 @@ _forgit_checkout_file() {
|
|||
files=()
|
||||
while IFS='' read -r file; do
|
||||
files+=("$file")
|
||||
done < <(_forgit_list_files --modified |
|
||||
done < <(_forgit_list_modified_files |
|
||||
FZF_DEFAULT_OPTS="$opts" fzf)
|
||||
[[ ${#files[@]} -gt 0 ]] && _forgit_git_checkout_file "$@" "${files[@]}"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,6 +44,28 @@ function test_forgit_worktree_changes_contains_untracked() {
|
|||
assert_contains "untracked_file.txt" "$output"
|
||||
}
|
||||
|
||||
function test_forgit_list_files_matches_modified_files_until_untracked_files_exist() {
|
||||
local list_files_output modified_files_output
|
||||
|
||||
list_files_output=$(_forgit_list_files --modified)
|
||||
modified_files_output=$(_forgit_list_modified_files)
|
||||
|
||||
assert_same "$modified_files_output" "$list_files_output"
|
||||
assert_contains "modified file.txt" "$list_files_output"
|
||||
|
||||
touch new_untracked_file.txt
|
||||
|
||||
list_files_output=$(_forgit_list_files --exclude-standard --modified --others)
|
||||
modified_files_output=$(_forgit_list_modified_files)
|
||||
|
||||
assert_contains "modified file.txt" "$list_files_output"
|
||||
assert_contains "new_untracked_file.txt" "$list_files_output"
|
||||
assert_contains "modified file.txt" "$modified_files_output"
|
||||
assert_not_contains "new_untracked_file.txt" "$modified_files_output"
|
||||
|
||||
rm new_untracked_file.txt
|
||||
}
|
||||
|
||||
function test_forgit_worktree_changes_excludes_staged() {
|
||||
local output
|
||||
|
||||
|
|
@ -114,6 +136,47 @@ function test_forgit_build_status_entries_uses_cwd_relative_display_paths_for_si
|
|||
assert_contains "../dir1/file.txt" "$output"
|
||||
}
|
||||
|
||||
function test_forgit_list_modified_files_uses_cwd_relative_paths_for_sibling_entries() {
|
||||
local output
|
||||
|
||||
mkdir -p dir1 dir2
|
||||
touch dir1/file.txt
|
||||
git add .
|
||||
git commit -m "add dirs" --quiet
|
||||
echo modified >dir1/file.txt
|
||||
cd dir2 || return 1
|
||||
|
||||
output=$(_forgit_list_modified_files)
|
||||
|
||||
assert_same "../dir1/file.txt" "$output"
|
||||
}
|
||||
|
||||
function test_forgit_rewrite_repo_paths_for_cwd_rewrites_repo_relative_paths() {
|
||||
local output rootdir
|
||||
|
||||
mkdir -p dir1 dir2
|
||||
touch dir1/file.txt
|
||||
rootdir=$(git rev-parse --show-toplevel)
|
||||
cd dir2 || return 1
|
||||
|
||||
output=$(printf 'dir1/file.txt\n' | _forgit_rewrite_repo_paths_for_cwd "$rootdir")
|
||||
|
||||
assert_same "../dir1/file.txt" "$output"
|
||||
}
|
||||
|
||||
function test_forgit_rewrite_repo_paths_for_cwd_formats_status_entries() {
|
||||
local output rootdir
|
||||
|
||||
mkdir -p dir1 dir2
|
||||
touch dir1/file.txt
|
||||
rootdir=$(git rev-parse --show-toplevel)
|
||||
cd dir2 || return 1
|
||||
|
||||
output=$(printf ' M dir1/file.txt\n' | _forgit_rewrite_repo_paths_for_cwd "$rootdir" status_entries "$_ffsep")
|
||||
|
||||
assert_same "[ M] ../dir1/file.txt${_ffsep}${rootdir}/dir1/file.txt" "$output"
|
||||
}
|
||||
|
||||
function test_forgit_build_status_entries_uses_cwd_relative_display_paths_from_logical_symlink_paths() {
|
||||
local output sandbox
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue