-
Notifications
You must be signed in to change notification settings - Fork 3.6k
Fix file tree not loading when opened immediately after cloning a repo #9857
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
25fca58
8c6b8b6
a374f02
3a195f3
ed4c0d0
a397276
b0e8a75
daf445d
ecb414d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -10,13 +10,14 @@ use repo_metadata::local_model::IndexedRepoState; | |||||||||||||||||||||||||||||
| use repo_metadata::FileTreeEntry; | ||||||||||||||||||||||||||||||
| use repo_metadata::RepoMetadataModel; | ||||||||||||||||||||||||||||||
| use std::collections::{HashMap, HashSet}; | ||||||||||||||||||||||||||||||
| use std::io::Read; | ||||||||||||||||||||||||||||||
| use std::ops::Range; | ||||||||||||||||||||||||||||||
| use std::path::{Path, PathBuf}; | ||||||||||||||||||||||||||||||
| use std::sync::Arc; | ||||||||||||||||||||||||||||||
| use warp_util::path::LineAndColumnArg; | ||||||||||||||||||||||||||||||
| use warp_util::standardized_path::StandardizedPath; | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| use repo_metadata::repositories::DetectedRepositories; | ||||||||||||||||||||||||||||||
| use repo_metadata::repositories::{DetectedRepositories, RepoDetectionSource}; | ||||||||||||||||||||||||||||||
| use warp_core::send_telemetry_from_ctx; | ||||||||||||||||||||||||||||||
| use warpui::elements::{ | ||||||||||||||||||||||||||||||
| AcceptedByDropTarget, Align, Clipped, ConstrainedBox, Container, Dismiss, Draggable, | ||||||||||||||||||||||||||||||
|
|
@@ -1533,6 +1534,58 @@ impl FileTreeView { | |||||||||||||||||||||||||||||
| // When the file tree is active, index the lazy-loaded path through the | ||||||||||||||||||||||||||||||
| // model so that a file watcher is started. | ||||||||||||||||||||||||||||||
| if self.is_active && !self.registered_lazy_loaded_paths.contains(path) { | ||||||||||||||||||||||||||||||
| // Check if this path appears to be a valid git repository by looking for .git directory | ||||||||||||||||||||||||||||||
| // or .git file (used by worktrees/submodules). For .git files, we need to parse the | ||||||||||||||||||||||||||||||
| // gitdir path and verify the actual git directory has a valid HEAD. | ||||||||||||||||||||||||||||||
| // Cap .git file read to 1KB to prevent unbounded reads from malformed local folders. | ||||||||||||||||||||||||||||||
| let has_git_entry = path | ||||||||||||||||||||||||||||||
| .to_local_path() | ||||||||||||||||||||||||||||||
| .map(|p| { | ||||||||||||||||||||||||||||||
| let git_entry = p.join(".git"); | ||||||||||||||||||||||||||||||
| if git_entry.is_dir() { | ||||||||||||||||||||||||||||||
| // Standard .git directory - verify HEAD exists | ||||||||||||||||||||||||||||||
| git_entry.join("HEAD").is_file() | ||||||||||||||||||||||||||||||
| } else if git_entry.is_file() { | ||||||||||||||||||||||||||||||
| // .git file (worktree/submodule) - read gitdir path and verify HEAD there | ||||||||||||||||||||||||||||||
| // Cap read to 1KB to prevent malicious/malformed folders from stalling UI. | ||||||||||||||||||||||||||||||
| std::fs::File::open(&git_entry) | ||||||||||||||||||||||||||||||
| .ok() | ||||||||||||||||||||||||||||||
| .and_then(|mut file| { | ||||||||||||||||||||||||||||||
| let mut contents = vec![0u8; 1024]; | ||||||||||||||||||||||||||||||
| let n = file.read(&mut contents).ok()?; | ||||||||||||||||||||||||||||||
| contents.truncate(n); | ||||||||||||||||||||||||||||||
| String::from_utf8(contents).ok() | ||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||
| .and_then(|contents| { | ||||||||||||||||||||||||||||||
| contents | ||||||||||||||||||||||||||||||
| .trim() | ||||||||||||||||||||||||||||||
| .strip_prefix("gitdir:") | ||||||||||||||||||||||||||||||
| .map(|gitdir| PathBuf::from(gitdir.trim())) | ||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||
| .map(|gitdir| { | ||||||||||||||||||||||||||||||
| let gitdir = if gitdir.is_absolute() { | ||||||||||||||||||||||||||||||
| gitdir | ||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||
| p.join(gitdir) | ||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||
| gitdir.join("HEAD").is_file() | ||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||
| .unwrap_or(false) | ||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||
| false | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||
| .unwrap_or(false); | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| // Check if this repo has already been attempted and failed. If so, fallback | ||||||||||||||||||||||||||||||
| // to lazy-loading to avoid repeatedly retrying failed detection. | ||||||||||||||||||||||||||||||
| let repo_id = repo_metadata::RepositoryIdentifier::local(path.clone()); | ||||||||||||||||||||||||||||||
| let repo_state = RepoMetadataModel::as_ref(ctx).repository_state(&repo_id, ctx); | ||||||||||||||||||||||||||||||
| let previously_failed = matches!(repo_state, Some(IndexedRepoState::Failed(_))); | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| // Always use lazy-loading as a fallback in case detection fails or returns None. | ||||||||||||||||||||||||||||||
| // This ensures the tree is never empty - detection will update it with full content | ||||||||||||||||||||||||||||||
| // if successful, otherwise lazy-loaded content remains visible. | ||||||||||||||||||||||||||||||
| let index_result = self | ||||||||||||||||||||||||||||||
| .repository_metadata_model | ||||||||||||||||||||||||||||||
| .update(ctx, |model: &mut RepoMetadataModel, ctx| { | ||||||||||||||||||||||||||||||
|
|
@@ -1552,6 +1605,18 @@ impl FileTreeView { | |||||||||||||||||||||||||||||
| if RepoMetadataModel::as_ref(ctx).is_lazy_loaded_path(path, ctx) { | ||||||||||||||||||||||||||||||
| self.registered_lazy_loaded_paths.insert(path.clone()); | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| // Additionally, if this appears to be a git repo and hasn't failed before, | ||||||||||||||||||||||||||||||
| // trigger proper detection to get full repo indexing instead of shallow tree. | ||||||||||||||||||||||||||||||
| if has_git_entry && !previously_failed { | ||||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. /oz-review The code at 8c6b8b6:
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 [SUGGESTION] This new detection path is not covered by the existing file tree tests; add a regression case where an active tree opens a repo root with
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||||||||||||||||||||||||||||||
| DetectedRepositories::handle(ctx).update(ctx, |repos, ctx| { | ||||||||||||||||||||||||||||||
| std::mem::drop(repos.detect_possible_git_repo( | ||||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||||||||||||||||||||||||||||||
| &path.to_string(), | ||||||||||||||||||||||||||||||
| RepoDetectionSource::TerminalNavigation, | ||||||||||||||||||||||||||||||
| ctx, | ||||||||||||||||||||||||||||||
| )); | ||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||
|
Chukwuebuka-2003 marked this conversation as resolved.
Comment on lines
+1612
to
1618
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🚨 [CRITICAL] This drops the
Suggested change
|
||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| let id = repo_metadata::RepositoryIdentifier::local(path.clone()); | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -303,3 +303,52 @@ pub fn test_file_tree_nested_file_opening() -> Builder { | |
| .add_assertion(assert_pane_title(0, 1, Regex::new(r"helper\.js$").unwrap())), | ||
| ) | ||
| } | ||
|
|
||
| /// Regression test for issue #9846: File tree sidebar does not load when opened | ||
| /// immediately after cloning a repository. | ||
| /// | ||
| /// This test verifies that when the file tree is opened on a directory that | ||
| /// contains a valid .git entry, the proper git detection is triggered instead | ||
| /// of falling back to shallow lazy-loading. | ||
| pub fn test_file_tree_loads_git_repo_on_first_open() -> Builder { | ||
| new_builder() | ||
| .with_setup(|utils| { | ||
| let test_dir = utils.test_dir(); | ||
|
|
||
| // Create a valid git repository structure (simulating a freshly cloned repo) | ||
| let git_dir = test_dir.join(".git"); | ||
| std::fs::create_dir_all(&git_dir).expect("Failed to create .git directory"); | ||
|
|
||
| // Create a valid HEAD file (required for valid git repo detection) | ||
| std::fs::write(git_dir.join("HEAD"), "ref: refs/heads/main\n") | ||
| .expect("Failed to create HEAD file"); | ||
|
|
||
| // Create some files in the repo to verify the full tree loads | ||
| std::fs::write(test_dir.join("README.md"), "# Test Repo").expect("Failed to create README"); | ||
| std::fs::create_dir_all(test_dir.join("src")).expect("Failed to create src dir"); | ||
| std::fs::write(test_dir.join("src/main.rs"), "fn main() {}") | ||
| .expect("Failed to create main.rs"); | ||
|
|
||
| let dir_string = test_dir | ||
| .to_str() | ||
| .expect("Should be able to convert test dir to str"); | ||
| write_all_rc_files_for_test(&test_dir, format!("cd {dir_string}")); | ||
| }) | ||
| .with_step(wait_until_bootstrapped_single_pane_for_tab(0)) | ||
| .with_step( | ||
| new_step_with_default_assertions("Open file tree panel") | ||
| .with_action(|app, _, _| open_file_tree_panel(app)), | ||
| ) | ||
| // With the fix, detection triggers immediately on .git dirs, so the repo | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 [SUGGESTION] This test waits for terminal bootstrap and then expands |
||
| // should NOT be in lazy-loaded state. We verify by clicking on main.rs | ||
| // which only works if full indexing happened (lazy-loading loads shallow tree). | ||
| // Note: This tests the detection path; old lazy-loading would need expand. | ||
| .with_step( | ||
| new_step_with_default_assertions("Expand src to verify nested content loads") | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 [SUGGESTION] This still passes with the old lazy-load behavior because expanding |
||
| .with_click_on_saved_position("file_tree_item:src"), | ||
| ) | ||
| .with_step( | ||
| new_step_with_default_assertions("Verify main.rs visible (proves full tree loaded)") | ||
| .with_click_on_saved_position("file_tree_item:main.rs"), | ||
| ) | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
detect_possible_git_reporeturnsNonewithout updatingRepoMetadataModelwhen.gitexists but is invalid/unreadable, so this branch skips lazy-loading and leaves the root empty indefinitely. Add a fallback when detection completes with no repo, or validate enough synchronously before bypassingindex_lazy_loaded_path.