upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorm0wer <m0wer@sgn.space>2026-03-29 16:45:52 +0200
committerDanConwayDev <DanConwayDev@protonmail.com>2026-03-30 16:29:23 +0000
commit820fd706a24be7a58554a27e411e120cfa28d9a6 (patch)
tree2b95e4f835122dabfcd1f72f637a12368a4c18f9 /src
parente3276e74bc45cb4fb8f158b8249bee3d12a0805f (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.rs14
-rw-r--r--src/lib/git/mod.rs133
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
1327async fn get_local_cache_database(git_repo_path: &Path) -> Result<NostrLMDB> { 1327async 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
1332async fn get_global_cache_database(git_repo_path: Option<&Path>) -> Result<NostrLMDB> { 1336async 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
106impl RepoActions for Repo { 106impl 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}