Skip to content

perf: speed up modified file lookup in large repositories#544

Open
cjappl wants to merge 1 commit into
mainfrom
capple/faster_lookup
Open

perf: speed up modified file lookup in large repositories#544
cjappl wants to merge 1 commit into
mainfrom
capple/faster_lookup

Conversation

@cjappl

@cjappl cjappl commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator

Check list

  • I have performed a self-review of my code
  • I have commented my code in hard-to-understand areas
  • I have added unit tests for my code

Description

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.

On a repo I use for work, comparing the raw git diff with the raw git ls-files for listing modified files is ~8x faster, the difference between ~1.5s and ~10s. The quality of life improvement is huge.

To get this change working when in sibling directories, I pulled out an existing perl script used in one of the other functions.

Type of change

  • Performance improvement
  • Refactor

Test environment

  • Shell
    • bash
    • zsh
    • fish
  • OS
    • Linux
    • Mac OS X
    • Windows
    • Others:

Summary by CodeRabbit

  • Bug Fixes

    • Improved modified-file listings when working from subdirectories, including correct relative paths for files in sibling directories.
    • Ensured restore and file checkout operations use accurate modified-file selections.
    • Prevented untracked files from appearing in modified-file-only results.
    • Standardized status and file path display across working-tree views.
  • Tests

    • Added coverage for path handling, modified-file listings, status entries, and untracked-file filtering.

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.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Changes

Working-tree path handling

Layer / File(s) Summary
Path rewriting and modified-file listing
bin/git-forgit, tests/working-tree-changes.test.sh
Adds shared cwd-relative path rewriting and _forgit_list_modified_files, with coverage for modified, sibling-directory, and untracked-file cases.
Status-entry normalization
bin/git-forgit, tests/working-tree-changes.test.sh
Routes formatted status entries through the shared rewrite helper while preserving status metadata and repository-relative paths.
Restore and checkout integration
bin/git-forgit
Uses _forgit_list_modified_files for restore fallback candidates and checkout-file checks and selection.Estimated code review effort: 3 (Moderate)

Sequence Diagram(s)

sequenceDiagram
  participant GitDiff
  participant ModifiedFiles
  participant PathRewriter
  participant RestoreCheckout
  GitDiff->>ModifiedFiles: Return null-delimited modified paths
  ModifiedFiles->>PathRewriter: Rewrite repository paths for cwd
  PathRewriter-->>ModifiedFiles: Return display paths
  ModifiedFiles->>RestoreCheckout: Provide selectable modified files
Loading

Possibly related PRs

  • wfxr/forgit#537: Both changes update how modified files are collected for the restore workflow.

Suggested reviewers: wfxr, carlfriedrich, sandr01d

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: faster modified-file lookup in large repositories.
Description check ✅ Passed The description includes a summary, motivation, change type, checklist items, and test environment details, matching the template well.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch capple/faster_lookup

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Comment thread bin/git-forgit
}
}
' "$rootdir" "$cwd" "$mode" "$separator"
}

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change was largely just pulled from _forgit_build_status_entries below, with a few ergonomic tweaks to make it work for both situations. The new tests should hopefully make sure this is stable

@cjappl

cjappl commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator Author

Also, hi! Long time no see. I see from my inbox you all have been super hard at work in this repo.

Please let me know if I've run afoul of any new norms or if I've missed anything.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
bin/git-forgit (1)

1131-1142: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Optional: capture the modified-file list once instead of invoking it twice.

_forgit_list_modified_files runs a full git diff + Perl pipeline at Line 1131 (count) and again at Line 1141 (fzf input). Since this PR targets responsiveness, capturing the result once avoids the redundant subprocess and closes the small window where the two calls could disagree.

♻️ Proposed single-invocation refactor
 _forgit_checkout_file() {
     _forgit_inside_work_tree || return 1
-    local files opts
+    local files opts modified_files
     _forgit_contains_non_flags "$@" && {
         _forgit_git_checkout_file "$@"
         return $?
     }
-    [[ $(_forgit_list_modified_files | wc -l) -eq 0 ]] && echo 'Nothing to checkout.' && return 1
+    modified_files=$(_forgit_list_modified_files)
+    [[ -z $modified_files ]] && echo 'Nothing to checkout.' && return 1
     opts="
         $FORGIT_FZF_DEFAULT_OPTS
         -m -0
         --preview=\"$FORGIT preview checkout_file_preview {}\"
         $FORGIT_CHECKOUT_FILE_FZF_OPTS
     "
     files=()
     while IFS='' read -r file; do
         files+=("$file")
-    done < <(_forgit_list_modified_files |
-        FZF_DEFAULT_OPTS="$opts" fzf)
+    done < <(printf '%s\n' "$modified_files" |
+        FZF_DEFAULT_OPTS="$opts" fzf)
     [[ ${`#files`[@]} -gt 0 ]] && _forgit_git_checkout_file "$@" "${files[@]}"
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@bin/git-forgit` around lines 1131 - 1142, Capture the output of
_forgit_list_modified_files once at the start of the checkout flow, use that
captured value for the empty-list check, and feed the same value into the fzf
input while preserving filename handling and array population. Remove the second
invocation so the modified-file list cannot change between checks.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@bin/git-forgit`:
- Around line 1131-1142: Capture the output of _forgit_list_modified_files once
at the start of the checkout flow, use that captured value for the empty-list
check, and feed the same value into the fzf input while preserving filename
handling and array population. Remove the second invocation so the modified-file
list cannot change between checks.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3b2ba7f4-5e0d-40af-bca2-fcadba08ddc4

📥 Commits

Reviewing files that changed from the base of the PR and between e0c7b84 and 2c97ddd.

📒 Files selected for processing (2)
  • bin/git-forgit
  • tests/working-tree-changes.test.sh

@cjappl

cjappl commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator Author

I like the coderabbit nitpick review, I think that's also a good improvement. I can either apply it here or do it in a follow up commit. Let me know what you think (human reviewers)

@sandr01d sandr01d left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good to see you here again!
I like this very much. I've run into git ls-files being slow on large repositories in the past too and this should give us a nice boost. Approved with two minor nitpicks.

Comment thread bin/git-forgit
Comment on lines +226 to +230
local cwd mode rootdir separator
rootdir=$1
mode=${2:-paths}
separator=${3-}
cwd=$(pwd -P)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: I'd put the definition and assignment of the local variables in the same order. I find it easier to read this way.

Suggested change
local cwd mode rootdir separator
rootdir=$1
mode=${2:-paths}
separator=${3-}
cwd=$(pwd -P)
local rootdir mode separator cwd
rootdir=$1
mode=${2:-paths}
separator=${3-}
cwd=$(pwd -P)

@sandr01d

Copy link
Copy Markdown
Collaborator

I like the coderabbit nitpick review, I think that's also a good improvement. I can either apply it here or do it in a follow up commit. Let me know what you think (human reviewers)

I agree. As to whether it should go in a follow up commit or not, I'm fine with both. Maybe leaning a bit more towards having a separate commit because it is a different kind of optimization, but fine either way.

Comment thread bin/git-forgit
local cwd mode rootdir separator
rootdir=$1
mode=${2:-paths}
separator=${3-}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is the more common version to do this because it also expands to an empty string if the variable is declared but is null. At least that's what I found when looking it up here.

Suggested change
separator=${3-}
separator=${3:-}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants