Git object storage and pack files for Eio
at main 172 lines 7.7 kB view raw
1(* Copyright (c) 2024-2026 Thomas Gazagnaire <thomas@gazagnaire.org> 2 3 Permission to use, copy, modify, and distribute this software for any 4 purpose with or without fee is hereby granted, provided that the above 5 copyright notice and this permission notice appear in all copies. 6 7 THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. *) 14 15let hash = Test_helpers.hash 16let with_temp_repo = Test_helpers.with_temp_repo 17let commit = Test_helpers.commit 18 19let test_list_main () = 20 with_temp_repo @@ fun fs tmp_dir -> 21 let repo = Git.Repository.init ~fs tmp_dir in 22 let wt = Git.Repository.worktree repo in 23 let worktrees = 24 Git.Worktree.list wt ~head:(Git.Repository.head repo) 25 ~current_branch:(Git.Repository.current_branch repo) 26 in 27 Alcotest.(check int) "one worktree" 1 (List.length worktrees); 28 let main = List.hd worktrees in 29 Alcotest.(check bool) "main path matches" true (Fpath.equal main.path tmp_dir) 30 31let test_add_basic () = 32 with_temp_repo @@ fun fs tmp_dir -> 33 let repo = Git.Repository.init ~fs tmp_dir in 34 let wt = Git.Repository.worktree repo in 35 let tree = Git.Repository.write_tree repo (Git.Tree.v []) in 36 let c1 = commit ~repo ~tree ~parents:[] ~message:"initial" in 37 Git.Repository.write_ref repo "refs/heads/main" c1; 38 let head_path = Eio.Path.(fs / Fpath.to_string tmp_dir / ".git" / "HEAD") in 39 Eio.Path.save ~create:(`Or_truncate 0o644) head_path "ref: refs/heads/main\n"; 40 let wt_path = Fpath.(tmp_dir / "worktrees" / "feature1") in 41 (match Git.Worktree.add wt ~head:c1 ~path:wt_path ~branch:"feature1" with 42 | Ok () -> () 43 | Error (`Msg msg) -> Alcotest.fail msg); 44 Alcotest.(check bool) 45 "worktree exists" true 46 (Git.Worktree.exists wt ~path:wt_path); 47 Alcotest.(check (option hash)) 48 "branch created" (Some c1) 49 (Git.Repository.read_ref repo "refs/heads/feature1") 50 51let test_exists_false () = 52 with_temp_repo @@ fun fs tmp_dir -> 53 let repo = Git.Repository.init ~fs tmp_dir in 54 let wt = Git.Repository.worktree repo in 55 let nonexistent = Fpath.(tmp_dir / "nonexistent") in 56 Alcotest.(check bool) 57 "non-existent worktree" false 58 (Git.Worktree.exists wt ~path:nonexistent) 59 60let test_remove () = 61 with_temp_repo @@ fun fs tmp_dir -> 62 let repo = Git.Repository.init ~fs tmp_dir in 63 let wt = Git.Repository.worktree repo in 64 let tree = Git.Repository.write_tree repo (Git.Tree.v []) in 65 let c1 = commit ~repo ~tree ~parents:[] ~message:"initial" in 66 Git.Repository.write_ref repo "refs/heads/main" c1; 67 let head_path = Eio.Path.(fs / Fpath.to_string tmp_dir / ".git" / "HEAD") in 68 Eio.Path.save ~create:(`Or_truncate 0o644) head_path "ref: refs/heads/main\n"; 69 let wt_path = Fpath.(tmp_dir / "worktrees" / "feature2") in 70 (match Git.Worktree.add wt ~head:c1 ~path:wt_path ~branch:"feature2" with 71 | Ok () -> () 72 | Error (`Msg msg) -> Alcotest.fail msg); 73 Alcotest.(check bool) 74 "worktree exists before remove" true 75 (Git.Worktree.exists wt ~path:wt_path); 76 (match Git.Worktree.remove wt ~path:wt_path ~force:false with 77 | Ok () -> () 78 | Error (`Msg msg) -> Alcotest.fail msg); 79 Alcotest.(check bool) 80 "worktree gone after remove" false 81 (Git.Worktree.exists wt ~path:wt_path) 82 83let test_remove_main_fails () = 84 with_temp_repo @@ fun fs tmp_dir -> 85 let repo = Git.Repository.init ~fs tmp_dir in 86 let wt = Git.Repository.worktree repo in 87 match Git.Worktree.remove wt ~path:tmp_dir ~force:false with 88 | Ok () -> Alcotest.fail "should not be able to remove main worktree" 89 | Error (`Msg _) -> () 90 91let test_list_multiple () = 92 with_temp_repo @@ fun fs tmp_dir -> 93 let repo = Git.Repository.init ~fs tmp_dir in 94 let wt = Git.Repository.worktree repo in 95 let tree = Git.Repository.write_tree repo (Git.Tree.v []) in 96 let c1 = commit ~repo ~tree ~parents:[] ~message:"initial" in 97 Git.Repository.write_ref repo "refs/heads/main" c1; 98 let head_path = Eio.Path.(fs / Fpath.to_string tmp_dir / ".git" / "HEAD") in 99 Eio.Path.save ~create:(`Or_truncate 0o644) head_path "ref: refs/heads/main\n"; 100 let wt1 = Fpath.(tmp_dir / "worktrees" / "wt1") in 101 let wt2 = Fpath.(tmp_dir / "worktrees" / "wt2") in 102 let wt3 = Fpath.(tmp_dir / "worktrees" / "wt3") in 103 (match Git.Worktree.add wt ~head:c1 ~path:wt1 ~branch:"wt1" with 104 | Ok () -> () 105 | Error (`Msg msg) -> Alcotest.fail msg); 106 (match Git.Worktree.add wt ~head:c1 ~path:wt2 ~branch:"wt2" with 107 | Ok () -> () 108 | Error (`Msg msg) -> Alcotest.fail msg); 109 (match Git.Worktree.add wt ~head:c1 ~path:wt3 ~branch:"wt3" with 110 | Ok () -> () 111 | Error (`Msg msg) -> Alcotest.fail msg); 112 let worktrees = 113 Git.Worktree.list wt ~head:(Git.Repository.head repo) 114 ~current_branch:(Git.Repository.current_branch repo) 115 in 116 Alcotest.(check int) "4 worktrees" 4 (List.length worktrees); 117 Alcotest.(check bool) "wt1 exists" true (Git.Worktree.exists wt ~path:wt1); 118 Alcotest.(check bool) "wt2 exists" true (Git.Worktree.exists wt ~path:wt2); 119 Alcotest.(check bool) "wt3 exists" true (Git.Worktree.exists wt ~path:wt3) 120 121let test_entry_branch () = 122 with_temp_repo @@ fun fs tmp_dir -> 123 let repo = Git.Repository.init ~fs tmp_dir in 124 let wt = Git.Repository.worktree repo in 125 let tree = Git.Repository.write_tree repo (Git.Tree.v []) in 126 let c1 = commit ~repo ~tree ~parents:[] ~message:"initial" in 127 Git.Repository.write_ref repo "refs/heads/main" c1; 128 let head_path = Eio.Path.(fs / Fpath.to_string tmp_dir / ".git" / "HEAD") in 129 Eio.Path.save ~create:(`Or_truncate 0o644) head_path "ref: refs/heads/main\n"; 130 let wt_path = Fpath.(tmp_dir / "worktrees" / "my-feature") in 131 (match Git.Worktree.add wt ~head:c1 ~path:wt_path ~branch:"my-feature" with 132 | Ok () -> () 133 | Error (`Msg msg) -> Alcotest.fail msg); 134 let worktrees = 135 Git.Worktree.list wt ~head:(Git.Repository.head repo) 136 ~current_branch:(Git.Repository.current_branch repo) 137 in 138 let wt_entry = 139 List.find_opt 140 (fun (e : Git.Worktree.entry) -> Fpath.equal e.path wt_path) 141 worktrees 142 in 143 match wt_entry with 144 | None -> Alcotest.fail "worktree not found in list" 145 | Some entry -> 146 Alcotest.(check (option string)) 147 "branch name" (Some "my-feature") entry.branch 148 149let test_add_needs_head () = 150 with_temp_repo @@ fun fs tmp_dir -> 151 let repo = Git.Repository.init ~fs tmp_dir in 152 let wt = Git.Repository.worktree repo in 153 let tree = Git.Repository.write_tree repo (Git.Tree.v []) in 154 let c1 = commit ~repo ~tree ~parents:[] ~message:"initial" in 155 let wt_path = Fpath.(tmp_dir / "worktrees" / "feature") in 156 match Git.Worktree.add wt ~head:c1 ~path:wt_path ~branch:"feature" with 157 | Ok () -> Alcotest.(check bool) "worktree created" true true 158 | Error (`Msg msg) -> Alcotest.fail msg 159 160let tests = 161 [ 162 Alcotest.test_case "list_main" `Quick test_list_main; 163 Alcotest.test_case "add_basic" `Quick test_add_basic; 164 Alcotest.test_case "exists_false" `Quick test_exists_false; 165 Alcotest.test_case "remove" `Quick test_remove; 166 Alcotest.test_case "remove_main_fails" `Quick test_remove_main_fails; 167 Alcotest.test_case "list_multiple" `Quick test_list_multiple; 168 Alcotest.test_case "entry_branch" `Quick test_entry_branch; 169 Alcotest.test_case "add_needs_head" `Quick test_add_needs_head; 170 ] 171 172let suite = ("worktree", tests)