A monorepo management tool for the agentic ages
at main 164 lines 5.2 kB view raw
1(** Project initialization for unpac. 2 3 Creates the bare repository structure and initial main worktree. *) 4 5let default_unpac_toml = {|[opam] 6repositories = [] 7# compiler = "5.4.0" 8 9# Vendor cache location (default: XDG cache directory) 10# vendor_cache = "/path/to/cache" 11 12[projects] 13# Projects will be added here 14|} 15 16let project_dune_project name = Printf.sprintf {|(lang dune 3.20) 17(name %s) 18|} name 19 20let project_dune = {|(vendored_dirs vendor) 21|} 22 23let project_gitignore = {|_build/ 24*.install 25|} 26 27let vendor_dune = {|(vendored_dirs opam) 28|} 29 30(** Initialize a new unpac project at the given path. *) 31let init ~proc_mgr ~fs path = 32 (* Convert relative paths to absolute *) 33 let abs_path = 34 if Filename.is_relative path then 35 Filename.concat (Sys.getcwd ()) path 36 else path 37 in 38 let root = Eio.Path.(fs / abs_path) in 39 40 (* Create root directory *) 41 Eio.Path.mkdirs ~exists_ok:false ~perm:0o755 root; 42 43 (* Initialize bare repository *) 44 let git_path = Eio.Path.(root / "git") in 45 Eio.Path.mkdirs ~exists_ok:false ~perm:0o755 git_path; 46 Git.run_exn ~proc_mgr ~cwd:git_path ["init"; "--bare"] |> ignore; 47 48 (* Create initial main branch with unpac.toml *) 49 (* First create a temporary worktree to make the initial commit *) 50 let main_path = Eio.Path.(root / "main") in 51 Eio.Path.mkdirs ~exists_ok:false ~perm:0o755 main_path; 52 53 (* Initialize as a regular repo temporarily to create first commit *) 54 Git.run_exn ~proc_mgr ~cwd:main_path ["init"] |> ignore; 55 56 (* Write unpac.toml *) 57 Eio.Path.save ~create:(`Or_truncate 0o644) 58 Eio.Path.(main_path / "unpac.toml") 59 default_unpac_toml; 60 61 (* Create initial commit *) 62 Git.run_exn ~proc_mgr ~cwd:main_path ["add"; "unpac.toml"] |> ignore; 63 Git.run_exn ~proc_mgr ~cwd:main_path 64 ["commit"; "-m"; "Initial commit"] |> ignore; 65 66 (* Rename branch to main if needed *) 67 Git.run_exn ~proc_mgr ~cwd:main_path ["branch"; "-M"; "main"] |> ignore; 68 69 (* Push to bare repo and convert to worktree *) 70 Git.run_exn ~proc_mgr ~cwd:main_path 71 ["remote"; "add"; "origin"; "../git"] |> ignore; 72 Git.run_exn ~proc_mgr ~cwd:main_path 73 ["push"; "-u"; "origin"; "main"] |> ignore; 74 75 (* Remove the temporary clone and add main as a worktree of the bare repo *) 76 Eio.Path.rmtree main_path; 77 78 (* Add main as a worktree of the bare repo *) 79 Git.run_exn ~proc_mgr ~cwd:git_path 80 ["worktree"; "add"; "../main"; "main"] |> ignore; 81 82 root 83 84(** Check if a path is an unpac project root. *) 85let is_unpac_root path = 86 Eio.Path.is_directory Eio.Path.(path / "git") && 87 Eio.Path.is_directory Eio.Path.(path / "main") && 88 Eio.Path.is_file Eio.Path.(path / "main" / "unpac.toml") 89 90(** Find the unpac root by walking up from current directory. *) 91let find_root ~fs ~cwd = 92 let rec go path = 93 if is_unpac_root path then Some path 94 else match Eio.Path.split path with 95 | Some (parent, _) -> go parent 96 | None -> None 97 in 98 go Eio.Path.(fs / cwd) 99 100(** Create a new project branch with template. *) 101let create_project ~proc_mgr root name = 102 let project_path = Worktree.path root (Project name) in 103 104 (* Ensure project directory parent exists *) 105 let project_dir = Eio.Path.(root / "project") in 106 Eio.Path.mkdirs ~exists_ok:true ~perm:0o755 project_dir; 107 108 (* Create orphan branch *) 109 Worktree.ensure_orphan ~proc_mgr root (Project name); 110 111 (* Write template files *) 112 Eio.Path.save ~create:(`Or_truncate 0o644) 113 Eio.Path.(project_path / "dune-project") 114 (project_dune_project name); 115 116 Eio.Path.save ~create:(`Or_truncate 0o644) 117 Eio.Path.(project_path / "dune") 118 project_dune; 119 120 Eio.Path.save ~create:(`Or_truncate 0o644) 121 Eio.Path.(project_path / ".gitignore") 122 project_gitignore; 123 124 (* Create vendor directory structure with dune file *) 125 Eio.Path.mkdirs ~exists_ok:true ~perm:0o755 126 Eio.Path.(project_path / "vendor" / "opam"); 127 128 Eio.Path.save ~create:(`Or_truncate 0o644) 129 Eio.Path.(project_path / "vendor" / "dune") 130 vendor_dune; 131 132 (* Commit template *) 133 Git.run_exn ~proc_mgr ~cwd:project_path ["add"; "-A"] |> ignore; 134 Git.run_exn ~proc_mgr ~cwd:project_path 135 ["commit"; "-m"; "Initialize project " ^ name] |> ignore; 136 137 (* Update main/unpac.toml to register project *) 138 let main_path = Worktree.path root Main in 139 let toml_path = Eio.Path.(main_path / "unpac.toml") in 140 let content = Eio.Path.load toml_path in 141 142 (* Simple append to [projects] section - a proper implementation would parse TOML *) 143 let updated = 144 if content = "" || not (String.ends_with ~suffix:"\n" content) 145 then content ^ "\n" ^ name ^ " = {}\n" 146 else content ^ name ^ " = {}\n" 147 in 148 Eio.Path.save ~create:(`Or_truncate 0o644) toml_path updated; 149 150 Git.run_exn ~proc_mgr ~cwd:main_path ["add"; "unpac.toml"] |> ignore; 151 Git.run_exn ~proc_mgr ~cwd:main_path 152 ["commit"; "-m"; "Add project " ^ name] |> ignore; 153 154 project_path 155 156(** Remove a project branch and worktree. *) 157let remove_project ~proc_mgr root name = 158 (* Remove worktree if exists *) 159 Worktree.remove_force ~proc_mgr root (Project name); 160 161 (* Delete the branch *) 162 let git = Worktree.git_dir root in 163 let branch = Worktree.branch (Project name) in 164 Git.run_exn ~proc_mgr ~cwd:git ["branch"; "-D"; branch] |> ignore