(* Copyright (c) 2024-2026 Thomas Gazagnaire Permission to use, copy, modify, and distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. *) let hash = Test_helpers.hash let with_temp_repo = Test_helpers.with_temp_repo let commit = Test_helpers.commit let test_list_main () = with_temp_repo @@ fun fs tmp_dir -> let repo = Git.Repository.init ~fs tmp_dir in let wt = Git.Repository.worktree repo in let worktrees = Git.Worktree.list wt ~head:(Git.Repository.head repo) ~current_branch:(Git.Repository.current_branch repo) in Alcotest.(check int) "one worktree" 1 (List.length worktrees); let main = List.hd worktrees in Alcotest.(check bool) "main path matches" true (Fpath.equal main.path tmp_dir) let test_add_basic () = with_temp_repo @@ fun fs tmp_dir -> let repo = Git.Repository.init ~fs tmp_dir in let wt = Git.Repository.worktree repo in let tree = Git.Repository.write_tree repo (Git.Tree.v []) in let c1 = commit ~repo ~tree ~parents:[] ~message:"initial" in Git.Repository.write_ref repo "refs/heads/main" c1; let head_path = Eio.Path.(fs / Fpath.to_string tmp_dir / ".git" / "HEAD") in Eio.Path.save ~create:(`Or_truncate 0o644) head_path "ref: refs/heads/main\n"; let wt_path = Fpath.(tmp_dir / "worktrees" / "feature1") in (match Git.Worktree.add wt ~head:c1 ~path:wt_path ~branch:"feature1" with | Ok () -> () | Error (`Msg msg) -> Alcotest.fail msg); Alcotest.(check bool) "worktree exists" true (Git.Worktree.exists wt ~path:wt_path); Alcotest.(check (option hash)) "branch created" (Some c1) (Git.Repository.read_ref repo "refs/heads/feature1") let test_exists_false () = with_temp_repo @@ fun fs tmp_dir -> let repo = Git.Repository.init ~fs tmp_dir in let wt = Git.Repository.worktree repo in let nonexistent = Fpath.(tmp_dir / "nonexistent") in Alcotest.(check bool) "non-existent worktree" false (Git.Worktree.exists wt ~path:nonexistent) let test_remove () = with_temp_repo @@ fun fs tmp_dir -> let repo = Git.Repository.init ~fs tmp_dir in let wt = Git.Repository.worktree repo in let tree = Git.Repository.write_tree repo (Git.Tree.v []) in let c1 = commit ~repo ~tree ~parents:[] ~message:"initial" in Git.Repository.write_ref repo "refs/heads/main" c1; let head_path = Eio.Path.(fs / Fpath.to_string tmp_dir / ".git" / "HEAD") in Eio.Path.save ~create:(`Or_truncate 0o644) head_path "ref: refs/heads/main\n"; let wt_path = Fpath.(tmp_dir / "worktrees" / "feature2") in (match Git.Worktree.add wt ~head:c1 ~path:wt_path ~branch:"feature2" with | Ok () -> () | Error (`Msg msg) -> Alcotest.fail msg); Alcotest.(check bool) "worktree exists before remove" true (Git.Worktree.exists wt ~path:wt_path); (match Git.Worktree.remove wt ~path:wt_path ~force:false with | Ok () -> () | Error (`Msg msg) -> Alcotest.fail msg); Alcotest.(check bool) "worktree gone after remove" false (Git.Worktree.exists wt ~path:wt_path) let test_remove_main_fails () = with_temp_repo @@ fun fs tmp_dir -> let repo = Git.Repository.init ~fs tmp_dir in let wt = Git.Repository.worktree repo in match Git.Worktree.remove wt ~path:tmp_dir ~force:false with | Ok () -> Alcotest.fail "should not be able to remove main worktree" | Error (`Msg _) -> () let test_list_multiple () = with_temp_repo @@ fun fs tmp_dir -> let repo = Git.Repository.init ~fs tmp_dir in let wt = Git.Repository.worktree repo in let tree = Git.Repository.write_tree repo (Git.Tree.v []) in let c1 = commit ~repo ~tree ~parents:[] ~message:"initial" in Git.Repository.write_ref repo "refs/heads/main" c1; let head_path = Eio.Path.(fs / Fpath.to_string tmp_dir / ".git" / "HEAD") in Eio.Path.save ~create:(`Or_truncate 0o644) head_path "ref: refs/heads/main\n"; let wt1 = Fpath.(tmp_dir / "worktrees" / "wt1") in let wt2 = Fpath.(tmp_dir / "worktrees" / "wt2") in let wt3 = Fpath.(tmp_dir / "worktrees" / "wt3") in (match Git.Worktree.add wt ~head:c1 ~path:wt1 ~branch:"wt1" with | Ok () -> () | Error (`Msg msg) -> Alcotest.fail msg); (match Git.Worktree.add wt ~head:c1 ~path:wt2 ~branch:"wt2" with | Ok () -> () | Error (`Msg msg) -> Alcotest.fail msg); (match Git.Worktree.add wt ~head:c1 ~path:wt3 ~branch:"wt3" with | Ok () -> () | Error (`Msg msg) -> Alcotest.fail msg); let worktrees = Git.Worktree.list wt ~head:(Git.Repository.head repo) ~current_branch:(Git.Repository.current_branch repo) in Alcotest.(check int) "4 worktrees" 4 (List.length worktrees); Alcotest.(check bool) "wt1 exists" true (Git.Worktree.exists wt ~path:wt1); Alcotest.(check bool) "wt2 exists" true (Git.Worktree.exists wt ~path:wt2); Alcotest.(check bool) "wt3 exists" true (Git.Worktree.exists wt ~path:wt3) let test_entry_branch () = with_temp_repo @@ fun fs tmp_dir -> let repo = Git.Repository.init ~fs tmp_dir in let wt = Git.Repository.worktree repo in let tree = Git.Repository.write_tree repo (Git.Tree.v []) in let c1 = commit ~repo ~tree ~parents:[] ~message:"initial" in Git.Repository.write_ref repo "refs/heads/main" c1; let head_path = Eio.Path.(fs / Fpath.to_string tmp_dir / ".git" / "HEAD") in Eio.Path.save ~create:(`Or_truncate 0o644) head_path "ref: refs/heads/main\n"; let wt_path = Fpath.(tmp_dir / "worktrees" / "my-feature") in (match Git.Worktree.add wt ~head:c1 ~path:wt_path ~branch:"my-feature" with | Ok () -> () | Error (`Msg msg) -> Alcotest.fail msg); let worktrees = Git.Worktree.list wt ~head:(Git.Repository.head repo) ~current_branch:(Git.Repository.current_branch repo) in let wt_entry = List.find_opt (fun (e : Git.Worktree.entry) -> Fpath.equal e.path wt_path) worktrees in match wt_entry with | None -> Alcotest.fail "worktree not found in list" | Some entry -> Alcotest.(check (option string)) "branch name" (Some "my-feature") entry.branch let test_add_needs_head () = with_temp_repo @@ fun fs tmp_dir -> let repo = Git.Repository.init ~fs tmp_dir in let wt = Git.Repository.worktree repo in let tree = Git.Repository.write_tree repo (Git.Tree.v []) in let c1 = commit ~repo ~tree ~parents:[] ~message:"initial" in let wt_path = Fpath.(tmp_dir / "worktrees" / "feature") in match Git.Worktree.add wt ~head:c1 ~path:wt_path ~branch:"feature" with | Ok () -> Alcotest.(check bool) "worktree created" true true | Error (`Msg msg) -> Alcotest.fail msg let tests = [ Alcotest.test_case "list_main" `Quick test_list_main; Alcotest.test_case "add_basic" `Quick test_add_basic; Alcotest.test_case "exists_false" `Quick test_exists_false; Alcotest.test_case "remove" `Quick test_remove; Alcotest.test_case "remove_main_fails" `Quick test_remove_main_fails; Alcotest.test_case "list_multiple" `Quick test_list_multiple; Alcotest.test_case "entry_branch" `Quick test_entry_branch; Alcotest.test_case "add_needs_head" `Quick test_add_needs_head; ] let suite = ("worktree", tests)