A monorepo management tool for the agentic ages

more

+137 -65
+83 -47
bin/main.ml
··· 94 94 else 95 95 s 96 96 97 - (* URL rewriting for known mirrors *) 98 - let rewrite_git_url url = 99 - (* Rewrite erratique.ch repos to GitHub mirrors *) 100 - let erratique_prefix = "https://erratique.ch/repos/" in 101 - if String.length url > String.length erratique_prefix && 102 - String.sub url 0 (String.length erratique_prefix) = erratique_prefix then 103 - let rest = String.sub url (String.length erratique_prefix) 104 - (String.length url - String.length erratique_prefix) in 105 - "https://github.com/dbuenzli/" ^ rest 106 - else 107 - url 108 - 109 97 (* Source kind selection *) 110 98 let source_kind_term = 111 99 let git = ··· 331 319 | Unpac.Source.GitSource g -> g.url 332 320 | _ -> "https://" ^ url_str (* Fallback *) 333 321 in 334 - rewrite_git_url raw_url 322 + Unpac.Git_repo_lookup.rewrite_url raw_url 335 323 in 336 324 337 325 Format.printf " Adding %s (%d packages: %s)@." ··· 802 790 ] 803 791 in 804 792 let vendor_repo_arg = 805 - let doc = "Path to the vendor git repository." in 806 - Arg.(required & opt (some string) None & info ["vendor-repo"] ~docv:"DIR" ~doc) 793 + let doc = "Path to the vendor git repository (overrides config)." in 794 + Arg.(value & opt (some string) None & info ["vendor-repo"] ~docv:"DIR" ~doc) 807 795 in 808 - let run () config_path cache_dir resolve_deps vendor_repo package_specs = 796 + let run () config_path cache_dir resolve_deps vendor_repo_arg package_specs = 809 797 Eio_main.run @@ fun env -> 810 798 let fs = Eio.Stdenv.fs env in 811 799 let proc_mgr = Eio.Stdenv.process_mgr env in 812 - let vendor_path = Eio.Path.(fs / vendor_repo) in 813 800 814 801 if package_specs = [] then begin 815 802 Format.eprintf "Please specify at least one package.@."; 816 803 exit 1 817 804 end; 818 805 806 + (* Load config and determine vendor repo path *) 807 + let config = Unpac.Config.load_exn config_path in 808 + let vendor_repo = match vendor_repo_arg with 809 + | Some path -> path 810 + | None -> match config.opam.vendor_repo with 811 + | Some path -> path 812 + | None -> 813 + Format.eprintf "Error: No vendor-repo specified. Use --vendor-repo or set opam.vendor_repo in config.@."; 814 + exit 1 815 + in 816 + let vendor_path = Eio.Path.(fs / vendor_repo) in 817 + 819 818 (* Initialize vendor repo if needed *) 820 819 if not (Unpac.Git.is_repository vendor_path) then begin 821 820 Format.printf "Initializing vendor repository at %s@." vendor_repo; ··· 823 822 Unpac.Git.init ~proc_mgr ~cwd:vendor_path 824 823 end; 825 824 825 + (* Check for vendor-upstream remote in config (for fetching pre-vendored branches) *) 826 + let vendor_upstream_url = match vendor_repo_arg with 827 + | Some _ -> config.opam.vendor_repo (* If overriding, use config as upstream *) 828 + | None -> None (* No separate upstream if using config directly *) 829 + in 830 + let has_vendor_upstream = Option.is_some vendor_upstream_url in 831 + 832 + (* Setup vendor-upstream remote if configured *) 833 + (match vendor_upstream_url with 834 + | Some url -> 835 + Format.printf "Setting up vendor-upstream remote -> %s@." url; 836 + ignore (Unpac.Git.ensure_remote ~proc_mgr ~cwd:vendor_path ~name:"vendor-upstream" ~url); 837 + Unpac.Git.fetch ~proc_mgr ~cwd:vendor_path ~remote:"vendor-upstream" 838 + | None -> ()); 839 + 826 840 (* Load opam index and resolve packages *) 827 841 let index = load_index ~fs ~cache_dir config_path in 828 842 let compiler = get_compiler_spec config_path in ··· 845 859 846 860 (* Fetch each unique dev-repo *) 847 861 let fetched = ref 0 in 862 + let from_upstream = ref 0 in 848 863 let skipped = ref 0 in 849 864 List.iter (fun (group : Unpac.Source.grouped_sources) -> 850 865 match group.dev_repo with ··· 859 874 | [] -> "unknown" 860 875 in 861 876 862 - (* Get URL with rewrites *) 863 - let url = 864 - let raw_url = 865 - let first_pkg = List.hd group.packages in 866 - match first_pkg.source with 867 - | Unpac.Source.GitSource g -> g.url 868 - | _ -> "" 869 - in 870 - rewrite_git_url raw_url 877 + let upstream_branch = "opam/upstream/" ^ name in 878 + 879 + (* Check if branch already exists from vendor-upstream *) 880 + let found_in_vendor_upstream = 881 + if has_vendor_upstream then 882 + let ref_name = "vendor-upstream/" ^ upstream_branch in 883 + Option.is_some (Unpac.Git.rev_parse ~proc_mgr ~cwd:vendor_path ref_name) 884 + else false 871 885 in 872 886 873 - if url = "" then begin 874 - incr skipped; 875 - Format.printf " [SKIP] No git URL for %s@." name 887 + if found_in_vendor_upstream then begin 888 + (* Use branch from vendor-upstream *) 889 + let ref_point = "vendor-upstream/" ^ upstream_branch in 890 + Unpac.Git.branch_force ~proc_mgr ~cwd:vendor_path ~name:upstream_branch ~point:ref_point; 891 + incr fetched; 892 + incr from_upstream; 893 + Format.printf " [OK] %s -> %s (from vendor-upstream)@." name upstream_branch 876 894 end else begin 877 - Format.printf " Fetching %s from %s@." name url; 895 + (* Fall back to fetching from original upstream *) 896 + let url = 897 + let raw_url = 898 + let first_pkg = List.hd group.packages in 899 + match first_pkg.source with 900 + | Unpac.Source.GitSource g -> g.url 901 + | _ -> "" 902 + in 903 + Unpac.Git_repo_lookup.rewrite_url raw_url 904 + in 878 905 879 - let remote = "origin-" ^ name in 880 - let upstream_branch = "upstream/" ^ name in 906 + if url = "" then begin 907 + incr skipped; 908 + Format.printf " [SKIP] No git URL for %s@." name 909 + end else begin 910 + Format.printf " Fetching %s from %s@." name url; 881 911 882 - try 883 - (* Add/update remote *) 884 - ignore (Unpac.Git.ensure_remote ~proc_mgr ~cwd:vendor_path ~name:remote ~url); 912 + let remote = "origin-" ^ name in 885 913 886 - (* Fetch from remote *) 887 - Unpac.Git.fetch ~proc_mgr ~cwd:vendor_path ~remote; 914 + try 915 + (* Add/update remote *) 916 + ignore (Unpac.Git.ensure_remote ~proc_mgr ~cwd:vendor_path ~name:remote ~url); 888 917 889 - (* Detect default branch *) 890 - let default_branch = Unpac.Git.ls_remote_default_branch ~proc_mgr ~url in 891 - let ref_point = remote ^ "/" ^ default_branch in 918 + (* Fetch from remote *) 919 + Unpac.Git.fetch ~proc_mgr ~cwd:vendor_path ~remote; 920 + 921 + (* Detect default branch *) 922 + let default_branch = Unpac.Git.ls_remote_default_branch ~proc_mgr ~url in 923 + let ref_point = remote ^ "/" ^ default_branch in 892 924 893 - (* Create/update upstream branch *) 894 - Unpac.Git.branch_force ~proc_mgr ~cwd:vendor_path ~name:upstream_branch ~point:ref_point; 925 + (* Create/update upstream branch *) 926 + Unpac.Git.branch_force ~proc_mgr ~cwd:vendor_path ~name:upstream_branch ~point:ref_point; 895 927 896 - incr fetched; 897 - Format.printf " [OK] %s -> %s@." name upstream_branch 898 - with exn -> 899 - Format.eprintf " [FAIL] %s: %s@." name (format_error exn) 928 + incr fetched; 929 + Format.printf " [OK] %s -> %s@." name upstream_branch 930 + with exn -> 931 + Format.eprintf " [FAIL] %s: %s@." name (format_error exn) 932 + end 900 933 end 901 934 ) grouped; 902 935 903 - Format.printf "Done: %d fetched, %d skipped@." !fetched !skipped 936 + if has_vendor_upstream then 937 + Format.printf "Done: %d fetched (%d from vendor-upstream), %d skipped@." !fetched !from_upstream !skipped 938 + else 939 + Format.printf "Done: %d fetched, %d skipped@." !fetched !skipped 904 940 in 905 941 let info = Cmd.info "vendor-fetch" ~doc ~man in 906 942 Cmd.v info
+3 -1
lib/config.ml
··· 10 10 type opam_config = { 11 11 repositories : repo_config list; 12 12 compiler : string option; (* e.g., "ocaml.5.4.0" or "5.4.0" *) 13 + vendor_repo : string option; (* Path or URL to vendor repository *) 13 14 } 14 15 15 16 type t = { opam : opam_config } ··· 45 46 let opam_config_codec = 46 47 let open Tomlt in 47 48 let open Table in 48 - obj (fun repositories compiler -> { repositories; compiler }) 49 + obj (fun repositories compiler vendor_repo -> { repositories; compiler; vendor_repo }) 49 50 |> mem "repositories" (list repo_config_codec) 50 51 ~enc:(fun c -> c.repositories) 51 52 |> opt_mem "compiler" string ~enc:(fun c -> c.compiler) 53 + |> opt_mem "vendor_repo" string ~enc:(fun c -> c.vendor_repo) 52 54 |> finish 53 55 54 56 let codec =
+1
lib/config.mli
··· 18 18 type opam_config = { 19 19 repositories : repo_config list; 20 20 compiler : string option; (** Target compiler version, e.g. "5.4.0" or "ocaml.5.4.0" *) 21 + vendor_repo : string option; (** Path or URL to vendor repository with opam/vendor/* branches *) 21 22 } 22 23 (** Opam-specific configuration. *) 23 24
+32
lib/git_repo_lookup.ml
··· 1 + (** Git repository URL lookup and rewriting. 2 + 3 + This module handles URL rewriting for git repositories, mapping known 4 + slow upstream URLs to faster mirrors. *) 5 + 6 + (** Rewrite a git URL to use a faster mirror if available. 7 + 8 + Currently handles: 9 + - erratique.ch repos are mirrored on GitHub under dbuenzli *) 10 + let rewrite_url url = 11 + (* Rewrite erratique.ch repos to GitHub mirrors (faster) *) 12 + let erratique_https = "https://erratique.ch/repos/" in 13 + let erratique_http = "http://erratique.ch/repos/" in 14 + let github_mirror = "https://github.com/dbuenzli/" in 15 + if String.length url > String.length erratique_https 16 + && String.sub url 0 (String.length erratique_https) = erratique_https 17 + then 18 + let rest = 19 + String.sub url (String.length erratique_https) 20 + (String.length url - String.length erratique_https) 21 + in 22 + github_mirror ^ rest 23 + else if 24 + String.length url > String.length erratique_http 25 + && String.sub url 0 (String.length erratique_http) = erratique_http 26 + then 27 + let rest = 28 + String.sub url (String.length erratique_http) 29 + (String.length url - String.length erratique_http) 30 + in 31 + github_mirror ^ rest 32 + else url
+1
lib/unpac.ml
··· 2 2 3 3 module Config = Config 4 4 module Dev_repo = Dev_repo 5 + module Git_repo_lookup = Git_repo_lookup 5 6 module Repo_index = Repo_index 6 7 module Output = Output 7 8 module Source = Source
+4 -4
lib/vendor.ml
··· 62 62 (* Branch naming conventions *) 63 63 64 64 let remote_name pkg = "origin-" ^ pkg 65 - let upstream_branch pkg = "upstream/" ^ pkg 66 - let vendor_branch pkg = "vendor/" ^ pkg 67 - let patches_branch pkg = "patches/" ^ pkg 68 - let vendor_path pkg = "vendor/" ^ pkg ^ "/" 65 + let upstream_branch pkg = "opam/upstream/" ^ pkg 66 + let vendor_branch pkg = "opam/vendor/" ^ pkg 67 + let patches_branch pkg = "opam/patches/" ^ pkg 68 + let vendor_path pkg = "opam/vendor/" ^ pkg ^ "/" 69 69 70 70 (* Queries *) 71 71
+13 -13
lib/vendor.mli
··· 1 1 (** Vendor package management operations. 2 2 3 3 This module implements the three-tier branch model for vendoring packages: 4 - - [upstream/<pkg>] - pristine upstream with original paths 5 - - [vendor/<pkg>] - orphan branch with path-rewritten files 6 - - [patches/<pkg>] - local modifications on top of vendor *) 4 + - [opam/upstream/<pkg>] - pristine upstream with original paths 5 + - [opam/vendor/<pkg>] - orphan branch with path-rewritten files 6 + - [opam/patches/<pkg>] - local modifications on top of vendor *) 7 7 8 8 (** {1 Types} *) 9 9 ··· 54 54 (** [remote_name pkg] returns ["origin-<pkg>"] *) 55 55 56 56 val upstream_branch : string -> string 57 - (** [upstream_branch pkg] returns ["upstream/<pkg>"] *) 57 + (** [upstream_branch pkg] returns ["opam/upstream/<pkg>"] *) 58 58 59 59 val vendor_branch : string -> string 60 - (** [vendor_branch pkg] returns ["vendor/<pkg>"] *) 60 + (** [vendor_branch pkg] returns ["opam/vendor/<pkg>"] *) 61 61 62 62 val patches_branch : string -> string 63 - (** [patches_branch pkg] returns ["patches/<pkg>"] *) 63 + (** [patches_branch pkg] returns ["opam/patches/<pkg>"] *) 64 64 65 65 val vendor_path : string -> string 66 - (** [vendor_path pkg] returns ["vendor/<pkg>/"] *) 66 + (** [vendor_path pkg] returns ["opam/vendor/<pkg>/"] *) 67 67 68 68 (** {1 Queries} *) 69 69 ··· 108 108 This: 109 109 1. Adds remote [origin-<name>] 110 110 2. Fetches from the remote 111 - 3. Creates [upstream/<name>] branch (pristine) 112 - 4. Creates [vendor/<name>] orphan branch with path rewrite 113 - 5. Creates [patches/<name>] branch 114 - 6. Merges [patches/<name>] into current project branch 111 + 3. Creates [opam/upstream/<name>] branch (pristine) 112 + 4. Creates [opam/vendor/<name>] orphan branch with path rewrite 113 + 5. Creates [opam/patches/<name>] branch 114 + 6. Merges [opam/patches/<name>] into current project branch 115 115 7. Updates unpac.toml with package info *) 116 116 117 117 val update_package : ··· 123 123 124 124 This: 125 125 1. Fetches latest from [origin-<name>] 126 - 2. Updates [upstream/<name>] branch 127 - 3. Updates [vendor/<name>] branch with new import *) 126 + 2. Updates [opam/upstream/<name>] branch 127 + 3. Updates [opam/vendor/<name>] branch with new import *) 128 128 129 129 val rebase_patches : 130 130 proc_mgr:Git.proc_mgr ->