Monorepo management for opam overlays

Extract branch from url field instead of dev-repo

This is more compatible with opam conventions - dev-repo is just the
repository URL, while the url field's src can include a #branch fragment.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

+39 -4
+32 -2
lib/opam_repo.ml
··· 31 31 | true -> String.sub url 4 (String.length url - 4) 32 32 | false -> url 33 33 in 34 - Uri.of_string url 34 + let uri = Uri.of_string url in 35 + (* Strip fragment from dev-repo URL - branch comes from url field *) 36 + Uri.with_fragment uri None 37 + 38 + (** Extract branch from a URL string with optional #branch fragment *) 39 + let extract_branch_from_url url = 40 + let url = 41 + match String.starts_with ~prefix:"git+" url with 42 + | true -> String.sub url 4 (String.length url - 4) 43 + | false -> url 44 + in 45 + Uri.fragment (Uri.of_string url) 35 46 36 47 module OP = OpamParserTypes.FullPos 37 48 ··· 44 55 match item.pelem with 45 56 | OP.Variable (name, value) when name.pelem = "dev-repo" -> 46 57 extract_string_value value 58 + | _ -> None) 59 + items 60 + 61 + (** Find the 'src' field inside a 'url' section *) 62 + let find_url_src (items : OP.opamfile_item list) : string option = 63 + List.find_map 64 + (fun (item : OP.opamfile_item) -> 65 + match item.pelem with 66 + | OP.Section sec when sec.section_kind.pelem = "url" -> 67 + (* Look for src field inside the section *) 68 + List.find_map 69 + (fun (inner : OP.opamfile_item) -> 70 + match inner.pelem with 71 + | OP.Variable (name, value) when name.pelem = "src" -> 72 + extract_string_value value 73 + | _ -> None) 74 + sec.section_items.pelem 47 75 | _ -> None) 48 76 items 49 77 ··· 116 144 if not (is_git_url url) then Error (Not_git_remote (name, url)) 117 145 else 118 146 let dev_repo = normalize_git_url url in 147 + (* Extract branch from url field's src, not from dev-repo *) 148 + let branch = Option.bind (find_url_src opamfile.file_contents) extract_branch_from_url in 119 149 let depends = find_depends opamfile.file_contents in 120 150 let synopsis = find_synopsis opamfile.file_contents in 121 - Ok (Package.create ~name ~version ~dev_repo ~depends ?synopsis ()) 151 + Ok (Package.create ~name ~version ~dev_repo ?branch ~depends ?synopsis ()) 122 152 with 123 153 | Eio.Io _ as e -> Error (Io_error (Printexc.to_string e)) 124 154 | exn -> Error (Parse_error (path_str, Printexc.to_string exn)))
+7 -2
lib/opam_repo.mli
··· 71 71 72 72 val normalize_git_url : string -> Uri.t 73 73 (** [normalize_git_url url] normalizes a git URL by removing the "git+" prefix 74 - if present. 74 + and any fragment (branch) if present. 75 75 76 - For example, "git+https://example.com/repo.git" becomes 76 + For example, "git+https://example.com/repo.git#main" becomes 77 77 "https://example.com/repo.git". *) 78 + 79 + val extract_branch_from_url : string -> string option 80 + (** [extract_branch_from_url url] extracts the branch from a URL fragment. 81 + 82 + For example, "git+https://example.com/repo.git#main" returns [Some "main"]. *) 78 83 79 84 val scan_opam_files_for_deps : fs:_ Eio.Path.t -> Fpath.t -> string list 80 85 (** [scan_opam_files_for_deps ~fs dir_path] scans a directory for .opam files