···15431543 in
15441544 Cmd.v info Term.(ret (const run $ url_or_pkg_arg $ as_arg $ upstream_arg $ from_arg $ fork_url_arg $ dry_run_arg $ yes_arg $ logging_term))
1545154515461546+(* Rejoin command *)
15471547+15481548+let rejoin_cmd =
15491549+ let doc = "Add a source checkout back into the monorepo as a subtree" in
15501550+ let man =
15511551+ [
15521552+ `S Manpage.s_description;
15531553+ `P
15541554+ "Adds an existing src/<name>/ repository back into mono/<name>/ as a \
15551555+ subtree. This is useful after forking a package and removing it from \
15561556+ the monorepo with $(b,git rm).";
15571557+ `S "WORKFLOW";
15581558+ `P "Typical workflow for removing and re-adding a package:";
15591559+ `I ("1.", "Fork the package: $(b,monopam fork my-lib)");
15601560+ `I ("2.", "Remove from monorepo: $(b,git rm -r mono/my-lib && git commit)");
15611561+ `I ("3.", "Work on it in src/my-lib/");
15621562+ `I ("4.", "Re-add to monorepo: $(b,monopam rejoin my-lib)");
15631563+ `S "REQUIREMENTS";
15641564+ `P "For rejoin to work:";
15651565+ `I ("-", "src/<name>/ must exist and be a git repository");
15661566+ `I ("-", "mono/<name>/ must NOT exist (was removed)");
15671567+ `S "WHAT IT DOES";
15681568+ `P "The rejoin command:";
15691569+ `I ("1.", "Verifies src/<name>/ exists and is a git repo");
15701570+ `I ("2.", "Verifies mono/<name>/ does not exist");
15711571+ `I ("3.", "Prompts for confirmation (use $(b,--yes) to skip)");
15721572+ `I ("4.", "Uses $(b,git subtree add) to bring src/<name>/ into mono/<name>/");
15731573+ `S Manpage.s_examples;
15741574+ `P "Re-add a package from src/:";
15751575+ `Pre "monopam rejoin my-lib";
15761576+ `P "Preview what would be done:";
15771577+ `Pre "monopam rejoin my-lib --dry-run";
15781578+ `P "Rejoin without confirmation:";
15791579+ `Pre "monopam rejoin my-lib --yes";
15801580+ ]
15811581+ in
15821582+ let info = Cmd.info "rejoin" ~doc ~man in
15831583+ let name_arg =
15841584+ let doc = "Name of the subtree to rejoin (directory name under src/)" in
15851585+ Arg.(required & pos 0 (some string) None & info [] ~docv:"NAME" ~doc)
15861586+ in
15871587+ let dry_run_arg =
15881588+ let doc = "Show what would be done without making changes" in
15891589+ Arg.(value & flag & info [ "dry-run"; "n" ] ~doc)
15901590+ in
15911591+ let yes_arg =
15921592+ let doc = "Assume yes to all prompts (for automation)" in
15931593+ Arg.(value & flag & info [ "yes"; "y" ] ~doc)
15941594+ in
15951595+ let run name dry_run yes () =
15961596+ Eio_main.run @@ fun env ->
15971597+ with_verse_config env @@ fun config ->
15981598+ let fs = Eio.Stdenv.fs env in
15991599+ let proc = Eio.Stdenv.process_mgr env in
16001600+ (* Build the plan *)
16011601+ match Monopam.Fork_join.plan_rejoin ~proc ~fs ~config ~name ~dry_run () with
16021602+ | Error e ->
16031603+ Fmt.epr "Error: %a@." Monopam.Fork_join.pp_error_with_hint e;
16041604+ `Error (false, "rejoin failed")
16051605+ | Ok plan ->
16061606+ (* Print discovery and actions *)
16071607+ Fmt.pr "Analyzing rejoin request for '%s'...@.@." name;
16081608+ Fmt.pr "Discovery:@.%a@." Monopam.Fork_join.pp_discovery plan.discovery;
16091609+ Fmt.pr "@.Actions to perform:@.";
16101610+ List.iteri (fun i action ->
16111611+ Fmt.pr " %d. %a@." (i + 1) Monopam.Fork_join.pp_action action
16121612+ ) plan.actions;
16131613+ Fmt.pr "@.";
16141614+ (* Prompt for confirmation unless --yes or --dry-run *)
16151615+ let proceed =
16161616+ if dry_run then begin
16171617+ Fmt.pr "(dry-run mode - no changes will be made)@.";
16181618+ true
16191619+ end else if yes then
16201620+ true
16211621+ else
16221622+ confirm "Proceed?"
16231623+ in
16241624+ if not proceed then begin
16251625+ Fmt.pr "Cancelled.@.";
16261626+ `Ok ()
16271627+ end else begin
16281628+ (* Execute the plan *)
16291629+ match Monopam.Fork_join.execute_join_plan ~proc ~fs plan with
16301630+ | Ok result ->
16311631+ if not dry_run then begin
16321632+ Fmt.pr "%a@." Monopam.Fork_join.pp_join_result result;
16331633+ Fmt.pr "@.Next steps:@.";
16341634+ Fmt.pr " 1. Commit the changes: git add -A && git commit@.";
16351635+ Fmt.pr " 2. Run $(b,monopam sync) to synchronize@."
16361636+ end;
16371637+ `Ok ()
16381638+ | Error e ->
16391639+ Fmt.epr "Error: %a@." Monopam.Fork_join.pp_error_with_hint e;
16401640+ `Error (false, "rejoin failed")
16411641+ end
16421642+ in
16431643+ Cmd.v info Term.(ret (const run $ name_arg $ dry_run_arg $ yes_arg $ logging_term))
16441644+15461645(* Site command *)
1547164615481647let site_cmd =
···17421841 in
17431842 let info = Cmd.info "monopam" ~version:"%%VERSION%%" ~doc ~man in
17441843 Cmd.group info
17451745- [ init_cmd; status_cmd; diff_cmd; pull_cmd; cherrypick_cmd; sync_cmd; changes_cmd; opam_cmd; doctor_cmd; verse_cmd; feature_cmd; fork_cmd; join_cmd; devcontainer_cmd; site_cmd ]
18441844+ [ init_cmd; status_cmd; diff_cmd; pull_cmd; cherrypick_cmd; sync_cmd; changes_cmd; opam_cmd; doctor_cmd; verse_cmd; feature_cmd; fork_cmd; join_cmd; rejoin_cmd; devcontainer_cmd; site_cmd ]
1746184517471846let () = exit (Cmd.eval main_cmd)
+50
monopam/lib/fork_join.ml
···55 | Git_error of Git.error
66 | Subtree_not_found of string
77 | Src_already_exists of string
88+ | Src_not_found of string
89 | Subtree_already_exists of string
910 | No_opam_files of string
1011 | Verse_error of Verse.error
···5253 | Git_error e -> Fmt.pf ppf "Git error: %a" Git.pp_error e
5354 | Subtree_not_found name -> Fmt.pf ppf "Subtree not found in monorepo: %s" name
5455 | Src_already_exists name -> Fmt.pf ppf "Source checkout already exists: src/%s" name
5656+ | Src_not_found name -> Fmt.pf ppf "Source checkout not found: src/%s" name
5557 | Subtree_already_exists name -> Fmt.pf ppf "Subtree already exists in monorepo: mono/%s" name
5658 | No_opam_files name -> Fmt.pf ppf "No .opam files found in subtree: %s" name
5759 | Verse_error e -> Fmt.pf ppf "Verse error: %a" Verse.pp_error e
···6769 Some (Fmt.str "Check that mono/%s exists in your monorepo" name)
6870 | Src_already_exists name ->
6971 Some (Fmt.str "Remove or rename src/%s first, or choose a different name" name)
7272+ | Src_not_found name ->
7373+ Some (Fmt.str "Run 'monopam fork %s' first to create src/%s" name name)
7074 | Subtree_already_exists name ->
7175 Some (Fmt.str "Remove mono/%s first, or use a different name with --as" name)
7276 | No_opam_files name ->
···488492 } in
489493490494 Ok { discovery = { discovery with opam_files = opam_preview }; actions; result; dry_run }
495495+ end
496496+497497+(** Build a rejoin plan - add existing src/<name> back into mono/<name> *)
498498+let plan_rejoin ~proc ~fs ~config ~name ?(dry_run = false) () =
499499+ let monorepo = Verse_config.mono_path config in
500500+ let checkouts = Verse_config.src_path config in
501501+ let prefix = name in
502502+ let src_path = Fpath.(checkouts / name) in
503503+504504+ (* Gather discovery information *)
505505+ let subtree_exists = Git.Subtree.exists ~fs ~repo:monorepo ~prefix in
506506+ let src_exists = is_directory ~fs src_path in
507507+ let src_is_repo = if src_exists then Git.is_repo ~proc ~fs src_path else false in
508508+ let opam_files = if src_exists then find_opam_files ~fs src_path else [] in
509509+510510+ let discovery = {
511511+ mono_exists = subtree_exists;
512512+ src_exists;
513513+ has_subtree_history = false;
514514+ remote_accessible = None;
515515+ opam_files;
516516+ local_path_is_repo = Some src_is_repo;
517517+ } in
518518+519519+ (* Validation *)
520520+ if subtree_exists then
521521+ Error (Subtree_already_exists name)
522522+ else if not src_exists then
523523+ Error (Src_not_found name)
524524+ else if not src_is_repo then
525525+ Error (Config_error (Fmt.str "src/%s exists but is not a git repository" name))
526526+ else begin
527527+ let branch = Verse_config.default_branch in
528528+ let actions = [
529529+ Git_subtree_add { repo = monorepo; prefix; url = Uri.of_string (Fpath.to_string src_path); branch };
530530+ ] in
531531+532532+ let result = {
533533+ name;
534534+ source_url = Fpath.to_string src_path;
535535+ upstream_url = None;
536536+ packages_added = opam_files;
537537+ from_handle = None;
538538+ } in
539539+540540+ Ok { discovery; actions; result; dry_run }
491541 end
492542493543(** {1 Plan Execution} *)
+23
monopam/lib/fork_join.mli
···2020 | Git_error of Git.error (** Git operation failed *)
2121 | Subtree_not_found of string (** Subtree not found in monorepo *)
2222 | Src_already_exists of string (** Source checkout already exists *)
2323+ | Src_not_found of string (** Source checkout not found *)
2324 | Subtree_already_exists of string (** Subtree already exists in monorepo *)
2425 | No_opam_files of string (** No .opam files found in subtree *)
2526 | Verse_error of Verse.error (** Error from verse operations *)
···156157 @param source Git URL or local filesystem path to join
157158 @param name Override the subtree directory name (default: derived from source)
158159 @param upstream Original upstream URL if this is your fork
160160+ @param dry_run If true, mark plan as dry-run (execute will skip actions) *)
161161+162162+val plan_rejoin :
163163+ proc:_ Eio.Process.mgr ->
164164+ fs:Eio.Fs.dir_ty Eio.Path.t ->
165165+ config:Verse_config.t ->
166166+ name:string ->
167167+ ?dry_run:bool ->
168168+ unit ->
169169+ (join_result action_plan, error) result
170170+(** [plan_rejoin ~proc ~fs ~config ~name ?dry_run ()] builds a rejoin plan.
171171+172172+ This is used to add an existing src/<name>/ repository back into mono/<name>/
173173+ as a subtree. Useful after forking a package and removing it from the monorepo.
174174+175175+ Requires:
176176+ - src/<name>/ must exist and be a git repository
177177+ - mono/<name>/ must not exist
178178+179179+ The plan can be displayed to the user and executed with [execute_join_plan].
180180+181181+ @param name Name of the subtree (directory name under src/ and mono/)
159182 @param dry_run If true, mark plan as dry-run (execute will skip actions) *)
160183161184(** {1 Plan Execution} *)