Monorepo management for opam overlays
at main 182 lines 6.4 kB view raw
1(*--------------------------------------------------------------------------- 2 Copyright (c) 2026 Anil Madhavapeddy <anil@recoil.org>. All rights reserved. 3 SPDX-License-Identifier: ISC 4 ---------------------------------------------------------------------------*) 5 6(** High-level query interface for changes. 7 8 This module provides convenient functions for querying changes since a 9 specific timestamp and formatting them for broadcast. *) 10 11let changes_since ~fs ~changes_dir ~since ~now = 12 (* Get the date part of since for filtering *) 13 let since_date = 14 let (y, m, d), _ = Ptime.to_date_time since in 15 Printf.sprintf "%04d-%02d-%02d" y m d 16 in 17 (* Get current date for range end *) 18 let now_date = 19 let (y, m, d), _ = Ptime.to_date_time now in 20 Printf.sprintf "%04d-%02d-%02d" y m d 21 in 22 match 23 Changes_aggregated.load_range ~fs ~changes_dir ~from_date:since_date 24 ~to_date:now_date 25 with 26 | Error e -> Error e 27 | Ok aggregated_files -> 28 (* Filter to files generated after 'since' and collect entries *) 29 let entries = 30 List.concat_map 31 (fun (agg : Changes_aggregated.t) -> 32 if Ptime.compare agg.generated_at since > 0 then agg.entries else []) 33 aggregated_files 34 in 35 Ok entries 36 37let has_new_changes ~fs ~changes_dir ~since ~now = 38 match changes_since ~fs ~changes_dir ~since ~now with 39 | Ok entries -> entries <> [] 40 | Error _ -> false 41 42let format_repo_link repo url_opt = 43 match url_opt with 44 | Some url -> Printf.sprintf "[%s](%s)" repo url 45 | None -> repo (* No URL available, just use repo name *) 46 47let format_for_zulip ~entries ~include_date ~date = 48 if entries = [] then "No changes to report." 49 else begin 50 let buf = Buffer.create 1024 in 51 if include_date then begin 52 match date with 53 | Some d -> Buffer.add_string buf (Printf.sprintf "Updates for %s:\n\n" d) 54 | None -> Buffer.add_string buf "Recent updates:\n\n" 55 end; 56 (* Group by change type *) 57 let by_type = 58 [ 59 (Changes_aggregated.New_library, "New Libraries", []); 60 (Changes_aggregated.Feature, "Features", []); 61 (Changes_aggregated.Bugfix, "Bug Fixes", []); 62 (Changes_aggregated.Documentation, "Documentation", []); 63 (Changes_aggregated.Refactor, "Improvements", []); 64 (Changes_aggregated.Unknown, "Other Changes", []); 65 ] 66 in 67 let grouped = 68 List.map 69 (fun (ct, title, _) -> 70 let matching = 71 List.filter 72 (fun (e : Changes_aggregated.entry) -> e.change_type = ct) 73 entries 74 in 75 (ct, title, matching)) 76 by_type 77 in 78 List.iter 79 (fun (_ct, title, entries) -> 80 if entries <> [] then begin 81 Buffer.add_string buf (Printf.sprintf "### %s\n\n" title); 82 List.iter 83 (fun (entry : Changes_aggregated.entry) -> 84 let repo_link = 85 format_repo_link entry.repository entry.repo_url 86 in 87 Buffer.add_string buf 88 (Printf.sprintf "**%s**: %s\n" repo_link entry.summary); 89 List.iter 90 (fun change -> 91 Buffer.add_string buf (Printf.sprintf "- %s\n" change)) 92 entry.changes; 93 if entry.contributors <> [] then 94 Buffer.add_string buf 95 (Printf.sprintf "*Contributors: %s*\n" 96 (String.concat ", " entry.contributors)); 97 Buffer.add_string buf "\n") 98 entries 99 end) 100 grouped; 101 Buffer.contents buf 102 end 103 104let format_summary ~entries = 105 if entries = [] then "No new changes." 106 else 107 let count = List.length entries in 108 let repos = 109 List.sort_uniq String.compare 110 (List.map (fun (e : Changes_aggregated.entry) -> e.repository) entries) 111 in 112 Printf.sprintf "%d change%s across %d repositor%s: %s" count 113 (if count = 1 then "" else "s") 114 (List.length repos) 115 (if List.length repos = 1 then "y" else "ies") 116 (String.concat ", " repos) 117 118(** {1 Daily Changes (Real-time)} *) 119 120let daily_changes_since ~fs ~changes_dir ~since = 121 Changes_daily.entries_since ~fs ~changes_dir ~since 122 123let has_new_daily_changes ~fs ~changes_dir ~since = 124 daily_changes_since ~fs ~changes_dir ~since <> [] 125 126let format_daily_for_zulip ~entries ~include_date ~date = 127 if entries = [] then "No changes to report." 128 else begin 129 let buf = Buffer.create 1024 in 130 if include_date then begin 131 match date with 132 | Some d -> 133 Buffer.add_string buf (Printf.sprintf "## Changes for %s\n\n" d) 134 | None -> Buffer.add_string buf "## Recent Changes\n\n" 135 end; 136 (* Group by repository *) 137 let repos = 138 List.sort_uniq String.compare 139 (List.map (fun (e : Changes_daily.entry) -> e.repository) entries) 140 in 141 List.iter 142 (fun repo -> 143 let repo_entries = 144 List.filter 145 (fun (e : Changes_daily.entry) -> e.repository = repo) 146 entries 147 in 148 if repo_entries <> [] then begin 149 let first_entry = List.hd repo_entries in 150 let repo_link = format_repo_link repo first_entry.repo_url in 151 Buffer.add_string buf (Printf.sprintf "### %s\n\n" repo_link); 152 List.iter 153 (fun (entry : Changes_daily.entry) -> 154 Buffer.add_string buf (Printf.sprintf "**%s**\n" entry.summary); 155 List.iter 156 (fun change -> 157 Buffer.add_string buf (Printf.sprintf "- %s\n" change)) 158 entry.changes; 159 if entry.contributors <> [] then 160 Buffer.add_string buf 161 (Printf.sprintf "*Contributors: %s*\n" 162 (String.concat ", " entry.contributors)); 163 Buffer.add_string buf "\n") 164 repo_entries 165 end) 166 repos; 167 Buffer.contents buf 168 end 169 170let format_daily_summary ~entries = 171 if entries = [] then "No new changes." 172 else 173 let count = List.length entries in 174 let repos = 175 List.sort_uniq String.compare 176 (List.map (fun (e : Changes_daily.entry) -> e.repository) entries) 177 in 178 Printf.sprintf "%d change%s across %d repositor%s: %s" count 179 (if count = 1 then "" else "s") 180 (List.length repos) 181 (if List.length repos = 1 then "y" else "ies") 182 (String.concat ", " repos)