A CLI tool that generates an opam repository and monorepo from a list of git repos

allow using git submodules

+82 -5
+9 -3
bin/main.ml
··· 16 16 let doc = "Enable verbose output." in 17 17 Arg.(value & flag & info [ "v"; "verbose" ] ~doc) 18 18 19 - let run input_file opam_overlay output_dir verbose = 20 - let exit_code = Repo_tool.run ~input_file ~opam_overlay ~output_dir ~verbose in 19 + let use_submodules = 20 + let doc = "Add vendored repositories as git submodules instead of cloning them. This initializes the output directory as a git repository if needed." in 21 + Arg.(value & flag & info [ "submodules" ] ~doc) 22 + 23 + let run input_file opam_overlay output_dir use_submodules verbose = 24 + let exit_code = Repo_tool.run ~input_file ~opam_overlay ~output_dir ~use_submodules ~verbose in 21 25 exit exit_code 22 26 23 - let run_t = Term.(const run $ input_file $ opam_overlay $ output_dir $ verbose) 27 + let run_t = Term.(const run $ input_file $ opam_overlay $ output_dir $ use_submodules $ verbose) 24 28 25 29 let cmd = 26 30 let doc = "Generate an opam repository from git repositories" in ··· 38 42 `Pre " $(tname) --opam-overlay ~/opam-repo -o my-opam-repo"; 39 43 `P "Combine both sources (deduplicates by URL):"; 40 44 `Pre " $(tname) repos.txt --opam-overlay ~/opam-repo -o my-opam-repo"; 45 + `P "Use git submodules instead of cloning:"; 46 + `Pre " $(tname) repos.txt --submodules -o my-opam-repo"; 41 47 `P "Input file format:"; 42 48 `Pre 43 49 " https://github.com/user/repo1.git\n\
+73 -2
lib/repo_tool.ml
··· 62 62 Unix.mkdir path 0o755 63 63 end 64 64 65 + let is_git_repo dir = 66 + let git_dir = Filename.concat dir ".git" in 67 + Sys.file_exists git_dir 68 + 69 + let init_git_repo dir = 70 + if not (is_git_repo dir) then begin 71 + log "Initializing git repository in %s" dir; 72 + let cmd = Printf.sprintf "git -C %s init" dir in 73 + ignore (run_command cmd) 74 + end 75 + 76 + let add_or_update_submodule ~output_dir ~vendor_dir entry = 77 + let name = extract_repo_name entry.url in 78 + let target = Filename.concat vendor_dir name in 79 + let rel_path = "vendor/" ^ name in 80 + if Sys.file_exists target then begin 81 + log "Updating submodule %s" rel_path; 82 + let cmd = Printf.sprintf "git -C %s submodule update --remote %s 2>/dev/null" output_dir rel_path in 83 + match run_command cmd with 84 + | Ok () -> Ok target 85 + | Error _ -> 86 + (* Try to re-add the submodule *) 87 + log "Submodule update failed, trying to re-add %s" entry.url; 88 + let rm_cmd = Printf.sprintf "git -C %s rm -f %s 2>/dev/null" output_dir rel_path in 89 + ignore (run_command rm_cmd); 90 + let rm_dir_cmd = Printf.sprintf "rm -rf %s" target in 91 + ignore (run_command rm_dir_cmd); 92 + let branch_args = 93 + match entry.branch with 94 + | Some b -> Printf.sprintf "--branch %s" b 95 + | None -> "" 96 + in 97 + let add_cmd = 98 + Printf.sprintf "git -C %s submodule add --depth 1 %s %s %s" 99 + output_dir branch_args entry.url rel_path 100 + in 101 + match run_command add_cmd with 102 + | Ok () -> Ok target 103 + | Error msg -> 104 + Printf.eprintf "Failed to add submodule %s: %s\n%!" entry.url msg; 105 + Error msg 106 + end 107 + else begin 108 + log "Adding submodule %s to %s" entry.url rel_path; 109 + let branch_args = 110 + match entry.branch with 111 + | Some b -> Printf.sprintf "--branch %s" b 112 + | None -> "" 113 + in 114 + let cmd = 115 + Printf.sprintf "git -C %s submodule add --depth 1 %s %s %s" 116 + output_dir branch_args entry.url rel_path 117 + in 118 + match run_command cmd with 119 + | Ok () -> Ok target 120 + | Error msg -> 121 + Printf.eprintf "Failed to add submodule %s: %s\n%!" entry.url msg; 122 + Error msg 123 + end 124 + 65 125 let clone_or_update_repo ~vendor_dir entry = 66 126 let name = extract_repo_name entry.url in 67 127 let target = Filename.concat vendor_dir name in ··· 305 365 let packages = collect_all_packages vendor_dirs in 306 366 create_setup_script output_dir packages 307 367 308 - let run ~input_file ~opam_overlay ~output_dir ~verbose:v = 368 + let run ~input_file ~opam_overlay ~output_dir ~use_submodules ~verbose:v = 309 369 verbose := v; 310 370 let entries = 311 371 match (input_file, opam_overlay) with ··· 337 397 let vendor_dir = Filename.concat output_dir "vendor" in 338 398 mkdir_p vendor_dir; 339 399 log "Using vendor directory %s" vendor_dir; 400 + (* Initialize git repo if using submodules *) 401 + if use_submodules then begin 402 + init_git_repo output_dir; 403 + Printf.printf "Using git submodules for vendor dependencies\n%!" 404 + end; 340 405 let results = 341 406 List.map 342 407 (fun entry -> 343 - match clone_or_update_repo ~vendor_dir entry with 408 + let result = 409 + if use_submodules then 410 + add_or_update_submodule ~output_dir ~vendor_dir entry 411 + else 412 + clone_or_update_repo ~vendor_dir entry 413 + in 414 + match result with 344 415 | Ok repo_path -> 345 416 generate_repo_structure ~output_dir:opam_repo_dir ~repo_path ~git_url:entry.url; 346 417 Ok repo_path