type error = | Git_error of Git.error | Feature_exists of string | Feature_not_found of string | Config_error of string let pp_error ppf = function | Git_error e -> Fmt.pf ppf "Git error: %a" Git.pp_error e | Feature_exists name -> Fmt.pf ppf "Feature '%s' already exists" name | Feature_not_found name -> Fmt.pf ppf "Feature '%s' not found" name | Config_error msg -> Fmt.pf ppf "Configuration error: %s" msg let error_hint = function | Git_error _ -> Some "Check that the monorepo is properly initialized" | Feature_exists name -> Some (Printf.sprintf "Run 'monopam feature remove %s' first if you want to recreate it" name) | Feature_not_found name -> Some (Printf.sprintf "Run 'monopam feature list' to see available features, or 'monopam feature add %s' to create it" name) | Config_error _ -> Some "Run 'monopam init' to create a workspace configuration" let pp_error_with_hint ppf e = pp_error ppf e; match error_hint e with | Some hint -> Fmt.pf ppf "@.Hint: %s" hint | None -> () type entry = { name : string; path : Fpath.t; branch : string; } let pp_entry ppf e = Fmt.pf ppf "%s -> %a (branch: %s)" e.name Fpath.pp e.path e.branch (* Get the work directory path: root/work *) let work_path config = Fpath.(Verse_config.root config / "work") (* Get the feature worktree path: root/work/ *) let feature_path config name = Fpath.(work_path config / name) let add ~proc ~fs ~config ~name () = let mono = Verse_config.mono_path config in let work_dir = work_path config in let wt_path = feature_path config name in (* Check if feature already exists *) if Git.Worktree.exists ~proc ~fs ~repo:mono ~path:wt_path then Error (Feature_exists name) else begin (* Ensure work directory exists *) let work_eio = Eio.Path.(fs / Fpath.to_string work_dir) in (try Eio.Path.mkdirs ~perm:0o755 work_eio with Eio.Io _ -> ()); (* Create the worktree with a new branch *) match Git.Worktree.add ~proc ~fs ~repo:mono ~path:wt_path ~branch:name () with | Error e -> Error (Git_error e) | Ok () -> Ok { name; path = wt_path; branch = name } end let remove ~proc ~fs ~config ~name ~force () = let mono = Verse_config.mono_path config in let wt_path = feature_path config name in (* Check if feature exists *) if not (Git.Worktree.exists ~proc ~fs ~repo:mono ~path:wt_path) then Error (Feature_not_found name) else match Git.Worktree.remove ~proc ~fs ~repo:mono ~path:wt_path ~force () with | Error e -> Error (Git_error e) | Ok () -> Ok () let list ~proc ~fs ~config () = let mono = Verse_config.mono_path config in let work_dir = work_path config in let all_worktrees = Git.Worktree.list ~proc ~fs mono in (* Filter to only worktrees under work/ directory *) List.filter_map (fun (wt : Git.Worktree.entry) -> (* Check if this worktree is under the work directory *) let wt_str = Fpath.to_string wt.path in let work_str = Fpath.to_string work_dir in if String.starts_with ~prefix:work_str wt_str then let name = Fpath.basename wt.path in let branch = Option.value ~default:name wt.branch in Some { name; path = wt.path; branch } else None ) all_worktrees