diff options
| author | m0wer <m0wer@sgn.space> | 2026-03-29 16:45:52 +0200 |
|---|---|---|
| committer | DanConwayDev <DanConwayDev@protonmail.com> | 2026-03-30 16:29:23 +0000 |
| commit | 820fd706a24be7a58554a27e411e120cfa28d9a6 (patch) | |
| tree | 2b95e4f835122dabfcd1f72f637a12368a4c18f9 /src | |
| parent | e3276e74bc45cb4fb8f158b8249bee3d12a0805f (diff) | |
feat: git worktree support
Git worktrees don't have a .git directory with a parent, so we need to
look for the git dir via git2's Repository::discover() and then look for
the cache database there. This allows the client to work correctly when
run from a worktree, and also allows the cache database to be shared
between the main repo and its worktrees (since they share the same git
dir and thus the same cache path).
Diffstat (limited to 'src')
| -rw-r--r-- | src/lib/client.rs | 14 | ||||
| -rw-r--r-- | src/lib/git/mod.rs | 133 |
2 files changed, 140 insertions, 7 deletions
diff --git a/src/lib/client.rs b/src/lib/client.rs index 01662cd..86e4097 100644 --- a/src/lib/client.rs +++ b/src/lib/client.rs | |||
| @@ -1325,14 +1325,22 @@ fn pb_after_style(succeed: bool) -> indicatif::ProgressStyle { | |||
| 1325 | } | 1325 | } |
| 1326 | 1326 | ||
| 1327 | async fn get_local_cache_database(git_repo_path: &Path) -> Result<NostrLMDB> { | 1327 | async fn get_local_cache_database(git_repo_path: &Path) -> Result<NostrLMDB> { |
| 1328 | NostrLMDB::open(git_repo_path.join(".git/nostr-cache.lmdb")) | 1328 | let git_dir = git2::Repository::discover(git_repo_path) |
| 1329 | .context("failed to open or create nostr cache database at .git/nostr-cache.lmdb") | 1329 | .context("failed to discover git repository")? |
| 1330 | .commondir() | ||
| 1331 | .to_path_buf(); | ||
| 1332 | NostrLMDB::open(git_dir.join("nostr-cache.lmdb")) | ||
| 1333 | .context("failed to open or create nostr cache database at <git-dir>/nostr-cache.lmdb") | ||
| 1330 | } | 1334 | } |
| 1331 | 1335 | ||
| 1332 | async fn get_global_cache_database(git_repo_path: Option<&Path>) -> Result<NostrLMDB> { | 1336 | async fn get_global_cache_database(git_repo_path: Option<&Path>) -> Result<NostrLMDB> { |
| 1333 | let path = if std::env::var("NGITTEST").is_ok() { | 1337 | let path = if std::env::var("NGITTEST").is_ok() { |
| 1334 | if let Some(git_repo_path) = git_repo_path { | 1338 | if let Some(git_repo_path) = git_repo_path { |
| 1335 | git_repo_path.join(".git/test-global-cache.lmdb") | 1339 | let git_dir = git2::Repository::discover(git_repo_path) |
| 1340 | .context("failed to discover git repository")? | ||
| 1341 | .commondir() | ||
| 1342 | .to_path_buf(); | ||
| 1343 | git_dir.join("test-global-cache.lmdb") | ||
| 1336 | } else { | 1344 | } else { |
| 1337 | bail!("git_repo must be supplied to get_global_cache_database during integration tests") | 1345 | bail!("git_repo must be supplied to get_global_cache_database during integration tests") |
| 1338 | } | 1346 | } |
diff --git a/src/lib/git/mod.rs b/src/lib/git/mod.rs index 641349c..0001ca1 100644 --- a/src/lib/git/mod.rs +++ b/src/lib/git/mod.rs | |||
| @@ -105,10 +105,9 @@ pub trait RepoActions { | |||
| 105 | 105 | ||
| 106 | impl RepoActions for Repo { | 106 | impl RepoActions for Repo { |
| 107 | fn get_path(&self) -> Result<&Path> { | 107 | fn get_path(&self) -> Result<&Path> { |
| 108 | self.git_repo | 108 | self.git_repo.workdir().context( |
| 109 | .path() | 109 | "failed to find repository working directory (bare repositories are not supported)", |
| 110 | .parent() | 110 | ) |
| 111 | .context("failed to find repositiory path as .git has no parent") | ||
| 112 | } | 111 | } |
| 113 | 112 | ||
| 114 | fn get_origin_url(&self) -> Result<String> { | 113 | fn get_origin_url(&self) -> Result<String> { |
| @@ -2848,4 +2847,130 @@ index ce01362..a21e91c 100644\n\ | |||
| 2848 | } | 2847 | } |
| 2849 | } | 2848 | } |
| 2850 | 2849 | ||
| 2850 | mod worktree { | ||
| 2851 | use super::*; | ||
| 2852 | |||
| 2853 | #[test] | ||
| 2854 | fn get_path_returns_worktree_working_dir() -> Result<()> { | ||
| 2855 | let test_repo = GitTestRepo::default(); | ||
| 2856 | test_repo.populate()?; | ||
| 2857 | |||
| 2858 | let worktree_repo = test_repo.create_worktree("wt-branch")?; | ||
| 2859 | let git_repo = Repo::from_path(&worktree_repo.dir)?; | ||
| 2860 | |||
| 2861 | let path = git_repo.get_path()?; | ||
| 2862 | // get_path() should return the worktree's working directory, not | ||
| 2863 | // somewhere inside the main repo's .git/worktrees/ | ||
| 2864 | assert_eq!(path.canonicalize()?, worktree_repo.dir.canonicalize()?,); | ||
| 2865 | Ok(()) | ||
| 2866 | } | ||
| 2867 | |||
| 2868 | #[test] | ||
| 2869 | fn get_path_returns_normal_repo_working_dir() -> Result<()> { | ||
| 2870 | let test_repo = GitTestRepo::default(); | ||
| 2871 | test_repo.populate()?; | ||
| 2872 | |||
| 2873 | let git_repo = Repo::from_path(&test_repo.dir)?; | ||
| 2874 | let path = git_repo.get_path()?; | ||
| 2875 | assert_eq!(path.canonicalize()?, test_repo.dir.canonicalize()?,); | ||
| 2876 | Ok(()) | ||
| 2877 | } | ||
| 2878 | |||
| 2879 | #[test] | ||
| 2880 | fn from_path_works_with_worktree_dir() -> Result<()> { | ||
| 2881 | let test_repo = GitTestRepo::default(); | ||
| 2882 | test_repo.populate()?; | ||
| 2883 | |||
| 2884 | let worktree_repo = test_repo.create_worktree("wt-open")?; | ||
| 2885 | // Opening from the worktree's working directory should succeed | ||
| 2886 | let git_repo = Repo::from_path(&worktree_repo.dir)?; | ||
| 2887 | // And get_path() should return the worktree dir | ||
| 2888 | assert_eq!( | ||
| 2889 | git_repo.get_path()?.canonicalize()?, | ||
| 2890 | worktree_repo.dir.canonicalize()?, | ||
| 2891 | ); | ||
| 2892 | Ok(()) | ||
| 2893 | } | ||
| 2894 | |||
| 2895 | #[test] | ||
| 2896 | fn worktree_can_read_branches() -> Result<()> { | ||
| 2897 | let test_repo = GitTestRepo::default(); | ||
| 2898 | test_repo.populate()?; | ||
| 2899 | |||
| 2900 | let worktree_repo = test_repo.create_worktree("wt-branches")?; | ||
| 2901 | let git_repo = Repo::from_path(&worktree_repo.dir)?; | ||
| 2902 | |||
| 2903 | let branches = git_repo.get_local_branch_names()?; | ||
| 2904 | assert!(branches.contains(&"main".to_string())); | ||
| 2905 | assert!(branches.contains(&"wt-branches".to_string())); | ||
| 2906 | Ok(()) | ||
| 2907 | } | ||
| 2908 | |||
| 2909 | #[test] | ||
| 2910 | fn worktree_can_read_git_config() -> Result<()> { | ||
| 2911 | let test_repo = GitTestRepo::default(); | ||
| 2912 | test_repo.populate()?; | ||
| 2913 | |||
| 2914 | let worktree_repo = test_repo.create_worktree("wt-config")?; | ||
| 2915 | let git_repo = Repo::from_path(&worktree_repo.dir)?; | ||
| 2916 | |||
| 2917 | // nostr.repo is set by GitTestRepo::default() on the main repo | ||
| 2918 | // and should be readable from the worktree since config is shared | ||
| 2919 | let nostr_repo = git_repo.get_git_config_item("nostr.repo", None)?; | ||
| 2920 | assert!(nostr_repo.is_some()); | ||
| 2921 | Ok(()) | ||
| 2922 | } | ||
| 2923 | |||
| 2924 | #[test] | ||
| 2925 | fn worktree_get_head_commit_works() -> Result<()> { | ||
| 2926 | let test_repo = GitTestRepo::default(); | ||
| 2927 | test_repo.populate()?; | ||
| 2928 | |||
| 2929 | let worktree_repo = test_repo.create_worktree("wt-head")?; | ||
| 2930 | let git_repo = Repo::from_path(&worktree_repo.dir)?; | ||
| 2931 | |||
| 2932 | // Should not error - worktree has its own HEAD | ||
| 2933 | let _head = git_repo.get_head_commit()?; | ||
| 2934 | Ok(()) | ||
| 2935 | } | ||
| 2936 | |||
| 2937 | #[test] | ||
| 2938 | fn worktree_files_accessible_from_get_path() -> Result<()> { | ||
| 2939 | let test_repo = GitTestRepo::default(); | ||
| 2940 | test_repo.populate()?; | ||
| 2941 | |||
| 2942 | let worktree_repo = test_repo.create_worktree("wt-files")?; | ||
| 2943 | let git_repo = Repo::from_path(&worktree_repo.dir)?; | ||
| 2944 | |||
| 2945 | // Create a file in the worktree | ||
| 2946 | let test_file = worktree_repo.dir.join("worktree-test.txt"); | ||
| 2947 | fs::write(&test_file, "hello from worktree")?; | ||
| 2948 | |||
| 2949 | // get_path() should point to the worktree dir where the file lives | ||
| 2950 | let path = git_repo.get_path()?; | ||
| 2951 | assert!(path.join("worktree-test.txt").exists()); | ||
| 2952 | Ok(()) | ||
| 2953 | } | ||
| 2954 | |||
| 2955 | #[test] | ||
| 2956 | fn worktree_opened_via_git_dir_env_works() -> Result<()> { | ||
| 2957 | let test_repo = GitTestRepo::default(); | ||
| 2958 | test_repo.populate()?; | ||
| 2959 | |||
| 2960 | let worktree_repo = test_repo.create_worktree("wt-gitdir")?; | ||
| 2961 | |||
| 2962 | // In a worktree, GIT_DIR points to the worktree-specific git dir | ||
| 2963 | // (e.g., .git/worktrees/<name>). Simulate what git does when | ||
| 2964 | // calling a remote helper. | ||
| 2965 | let git_dir = worktree_repo.git_repo.path().to_path_buf(); | ||
| 2966 | let git_repo = Repo::from_path(&git_dir)?; | ||
| 2967 | |||
| 2968 | // get_path() should still return the worktree working directory | ||
| 2969 | assert_eq!( | ||
| 2970 | git_repo.get_path()?.canonicalize()?, | ||
| 2971 | worktree_repo.dir.canonicalize()?, | ||
| 2972 | ); | ||
| 2973 | Ok(()) | ||
| 2974 | } | ||
| 2975 | } | ||
| 2851 | } | 2976 | } |