use std::fs; use galaxy_util::local_or_remote_path::LocalOrRemotePath; use galaxy_util::standardized_path::StandardizedPath; use galaxyui_core::App; use virtual_fs::{Stub, VirtualFS}; use crate::repositories::{stub_git_repository, DetectedRepositories, RepoDetectionSource}; use crate::watcher::DirectoryWatcher; #[test] fn test_detect_possible_local_git_repo_non_existent_directory() { VirtualFS::test("detect_non_existent", |dirs, _vfs| { let non_existent_path = dirs.tests().join("non_existent_directory"); App::test((), |mut app| async move { let _watcher = app.add_model(DirectoryWatcher::new); let repo_handle = app.add_model(|_| DetectedRepositories::default()); repo_handle.update(&mut app, |watcher, ctx| { std::mem::drop(watcher.detect_possible_local_git_repo( &non_existent_path.to_string_lossy(), RepoDetectionSource::TerminalNavigation, ctx, )); }); // Since the path doesn't exist, canonicalization fails and so there's no spawned future. repo_handle.read(&app, |repos, _| { assert!(repos.spawned_futures().is_empty()); assert!(repos.repository_roots.is_empty()); }); }); }); } #[test] fn test_detect_possible_local_git_repo_not_a_git_repo() { VirtualFS::test("detect_not_git", |dirs, mut vfs| { // Create a regular directory structure without .git vfs.mkdir("regular_dir/subdir").with_files(vec![ Stub::FileWithContent("regular_dir/file1.txt", "content1"), Stub::FileWithContent("regular_dir/subdir/file2.txt", "content2"), ]); let regular_dir = dirs.tests().join("regular_dir"); App::test((), |mut app| async move { let _watcher = app.add_model(DirectoryWatcher::new); let repo_handle = app.add_model(|_| DetectedRepositories::default()); repo_handle.update(&mut app, |watcher, ctx| { std::mem::drop(watcher.detect_possible_local_git_repo( ®ular_dir.to_string_lossy(), RepoDetectionSource::TerminalNavigation, ctx, )); }); repo_handle .update(&mut app, |watcher, ctx| { let future_id = watcher.spawned_futures()[0]; ctx.await_spawned_future(future_id) }) .await; // Since no git repo is found, no repository should be registered. let regular_canonical = StandardizedPath::from_local_canonicalized(®ular_dir).unwrap(); repo_handle.read(&app, |watcher, _ctx| { let key = LocalOrRemotePath::Local(regular_canonical.to_local_path().unwrap()); assert!(!watcher.repository_roots.contains(&key)); }); }); }); } #[test] fn test_detect_possible_local_git_repo_nested_repo_created_after_parent_registration() { VirtualFS::test("detect_nested_repo", |dirs, mut vfs| { // Create a parent git repository structure stub_git_repository(&mut vfs, "parent_repo"); let parent_repo = dirs.tests().join("parent_repo"); let nested_project = parent_repo.join("projects/nested_project"); let parent_canonical_path = StandardizedPath::from_local_canonicalized(&parent_repo).unwrap(); App::test((), |mut app| async move { let watcher_handle = app.add_singleton_model(DirectoryWatcher::new); let repo_handle = app.add_model(|_| DetectedRepositories::default()); // First, register the parent repository // Now, try to detect the nested git repo. repo_handle .update(&mut app, |repo, ctx| { std::mem::drop(repo.detect_possible_local_git_repo( &parent_repo.to_string_lossy(), RepoDetectionSource::TerminalNavigation, ctx, )); let future_id = repo.spawned_futures().last().unwrap(); ctx.await_spawned_future(*future_id) }) .await; // Verify parent is registered repo_handle.read(&app, |repo, _ctx| { assert!(repo .get_root_for_path(&LocalOrRemotePath::Local( parent_canonical_path.to_local_path().unwrap(), )) .is_some()); }); // Now simulate creating a nested git repo in the projects/nested_project directory. // This would happen in real life when someone runs `git init` in a subdirectory. stub_git_repository(&mut vfs, "parent_repo/projects/nested_project"); // Now, try to detect the nested git repo. repo_handle .update(&mut app, |repo, ctx| { std::mem::drop(repo.detect_possible_local_git_repo( &nested_project.to_string_lossy(), RepoDetectionSource::TerminalNavigation, ctx, )); let future_id = repo.spawned_futures().last().unwrap(); ctx.await_spawned_future(*future_id) }) .await; // Verify that both repositories are now registered. let nested_canonical_path = StandardizedPath::from_local_canonicalized(&nested_project).unwrap(); repo_handle.read(&app, |repo, _ctx| { // Parent should still be registered assert!(repo .get_root_for_path(&LocalOrRemotePath::Local( parent_canonical_path.to_local_path().unwrap(), )) .is_some()); // Nested project should now also be registered as its own repo assert!(repo .get_root_for_path(&LocalOrRemotePath::Local( nested_canonical_path.to_local_path().unwrap(), )) .is_some()); }); // Check a subdirectory of the nested repo. This path must exist for canonicalization to succeed. let nested_project_contents = nested_project.join("src"); fs::create_dir(&nested_project_contents).expect("Creating nested directory failed"); // Verify that querying from the nested directory returns the nested repo, not the parent watcher_handle.read(&app, |watcher, ctx| { let found_handle = watcher.get_watched_directory_for_path(&nested_project_contents); assert!(found_handle.is_some()); assert_eq!( found_handle.unwrap().as_ref(ctx).root_dir(), &nested_canonical_path ); }); }); }); } #[test] #[cfg(feature = "local_fs")] fn test_find_git_repo_with_worktree() { VirtualFS::test("find_git_repo_worktree", |dirs, mut vfs| { // Set up a primary repository with a worktree directory. vfs.mkdir("main_repo/.git/objects"); vfs.mkdir("main_repo/.git/worktrees/wt1"); vfs.with_files(vec![ Stub::FileWithContent("main_repo/.git/HEAD", "ref: refs/heads/main"), // Worktree-specific gitdir contains a HEAD file Stub::FileWithContent("main_repo/.git/worktrees/wt1/HEAD", "ref: refs/heads/main"), ]); // Set up the worktree checkout directory with a gitfile pointing to the worktree gitdir. vfs.mkdir("checkout_wt1/src"); vfs.with_files(vec![Stub::FileWithContent( "checkout_wt1/.git", "gitdir: ../main_repo/.git/worktrees/wt1", )]); let worktree_root = dirs.tests().join("checkout_wt1"); let expected_gitdir = dirs.tests().join("main_repo/.git/worktrees/wt1"); App::test((), |mut _app| async move { let result = super::find_git_repo(worktree_root.as_path()).await; assert!(result.is_some(), "expected to find a git repo for worktree"); let info = result.unwrap(); // The working tree path should be the worktree checkout directory assert_eq!( info.working_tree_path.as_deref(), Some(worktree_root.as_path()) ); // The git_dir_path should resolve to the worktree's gitdir inside the primary repo. let actual = std::fs::canonicalize(&info.git_dir_path).expect("canonicalize gitdir"); let expected = std::fs::canonicalize(&expected_gitdir).expect("canonicalize expected"); assert_eq!(actual, expected); }); }); }