feat add favorite_repositories and archived_repositories config

New config.toml arrays allow partitioning repos on the root page: - favorite_repositories: displayed in a 'Favorites' section at the top - archived_repositories: displayed in an 'Archived' section at the bottom Entries are repo names (e.g. 'ogit.git') or directory names with a trailing slash (e.g. 'experiments/'). When both lists are empty the page renders as before without section headings.

Commit
5d725268674b31a97fc2eb0a243683da1f7108dd
Author
Marius Peter <dev@marius-peter.com>
Author date
Committer
Marius Peter <dev@marius-peter.com>
Committer date
Changed files
deploy/config.toml
index ccf411eb..608d88d9 100644..100644
@@ -2,7 +2,7 @@
2 2 # Place at /etc/ogit/config.toml (or set OGIT_CONFIG to override)
3 3
4 4 user = "git"
5 Removed: default_branch = "main"
5 Added: default_branch = "master"
6 6 git_project_root = "/srv/git"
7 7 commits_max_displayed = 10
8 8 host = "127.0.0.1"
lib/config.ml
index 39c28f4e..b8b34088 100644..100644
@@ -10,6 +10,8 @@
10 10 title : string;
11 11 host : string;
12 12 port : int;
13 Added: favorite_repositories : string list;
14 Added: archived_repositories : string list;
13 15 }
14 16
15 17 type load_error =
@@ -34,6 +36,8 @@
34 36 title = "";
35 37 host = "127.0.0.1";
36 38 port = 8081;
39 Added: favorite_repositories = [];
40 Added: archived_repositories = [];
37 41 }
38 42
39 43 let locate_config_file () =
@@ -47,6 +51,9 @@
47 51
48 52 let to_table t =
49 53 let open Types in
54 Added: let string_list_to_value lst =
55 Added: TArray (if lst = [] then NodeEmpty else NodeString lst)
56 Added: in
50 57 List.map
51 58 (fun (key, value) -> (Min.key key, value))
52 59 [
@@ -57,6 +64,8 @@
57 64 ("title", TString t.title);
58 65 ("host", TString t.host);
59 66 ("port", TInt t.port);
67 Added: ("favorite_repositories", string_list_to_value t.favorite_repositories);
68 Added: ("archived_repositories", string_list_to_value t.archived_repositories);
60 69 ]
61 70 |> Min.of_key_values
62 71
@@ -89,6 +98,14 @@
89 98 | Some _ -> Error (Invalid_value ("expected int for key: " ^ key))
90 99 | None -> Ok default
91 100
101 Added: let find_string_list_opt table key =
102 Added: match Types.Table.find_opt (Min.key key) table with
103 Added: | Some (Types.TArray (Types.NodeString values)) -> Ok values
104 Added: | Some (Types.TArray Types.NodeEmpty) -> Ok []
105 Added: | Some _ ->
106 Added: Error (Invalid_value ("expected array of strings for key: " ^ key))
107 Added: | None -> Ok []
108 Added:
92 109 let of_table table =
93 110 let ( let* ) = Result.bind in
94 111 let* git_project_root = find_string table "git_project_root" in
@@ -98,6 +115,12 @@
98 115 let* title = find_string_opt table "title" ~default:default.title in
99 116 let* host = find_string_opt table "host" ~default:default.host in
100 117 let* port = find_int_opt table "port" ~default:default.port in
118 Added: let* favorite_repositories =
119 Added: find_string_list_opt table "favorite_repositories"
120 Added: in
121 Added: let* archived_repositories =
122 Added: find_string_list_opt table "archived_repositories"
123 Added: in
101 124 if commits_max_displayed <= 0 then
102 125 Error (Invalid_value "commits_max_displayed must be positive")
103 126 else if port < 1 || port > 65535 then
@@ -112,6 +135,8 @@
112 135 title;
113 136 host;
114 137 port;
138 Added: favorite_repositories;
139 Added: archived_repositories;
115 140 }
116 141
117 142 let io_error file error =
lib/handlers.ml
index ddc8f47c..1a0222c2 100644..100644
@@ -49,9 +49,27 @@
49 49 Lwt.return (path, date))
50 50 repo_paths
51 51 in
52 Added: let node_name = function
53 Added: | Resolvers.Repo { repo_name; _ } -> repo_name
54 Added: | Resolvers.Directory (dir_name, _) -> dir_name ^ "/"
55 Added: in
56 Added: let is_favorite node =
57 Added: List.mem (node_name node) config.Config.favorite_repositories
58 Added: in
59 Added: let is_archived node =
60 Added: List.mem (node_name node) config.Config.archived_repositories
61 Added: in
62 Added: let favorites, rest = List.partition is_favorite nodes in
63 Added: let archived, regular = List.partition is_archived rest in
64 Added: (* Preserve the order specified in the config for favorites *)
65 Added: let sorted_favorites =
66 Added: List.filter_map
67 Added: (fun name -> List.find_opt (fun n -> node_name n = name) favorites)
68 Added: config.Config.favorite_repositories
69 Added: in
52 70 let readme = Resolvers.read_root_readme config in
53 71 Views.root ~user:config.Config.user ~root_title:(root_title config) ~dates
54 Removed: ?readme nodes
72 Added: ~favorites:sorted_favorites ~archived ?readme regular
55 73 | Error error -> error_response error
56 74
57 75 module Repo = struct
lib/views/root.ml
index 3558b297..350b351e 100644..100644
@@ -55,13 +55,41 @@
55 55 ];
56 56 ])
57 57
58 Removed: let render ~user ~root_title ~dates ?readme nodes =
59 Removed: let tree =
58 Added: let render ~user ~root_title ~dates ?(favorites = []) ?(archived = []) ?readme
59 Added: nodes =
60 Added: let repo_list nodes =
61 Added: HTML.(ul [] (List.map (li_of_fs_node ~prefix:"" ~dates) nodes))
62 Added: in
63 Added: let favorites_section =
64 Added: match favorites with
65 Added: | [] -> HTML.null []
66 Added: | _ ->
67 Added: HTML.(
68 Added: section
69 Added: [ class_ "repo-section repo-favorites" ]
70 Added: [ h3 [] [ txt "Favorites" ]; repo_list favorites ])
71 Added: in
72 Added: let main_section =
60 73 HTML.(
61 74 div
62 75 [ id "repositories" ]
63 Removed: [ ul [] (List.map (li_of_fs_node ~prefix:"" ~dates) nodes) ])
76 Added: (match (favorites, archived) with
77 Added: | [], [] ->
78 Added: (* No partitioning — render flat without a heading *)
79 Added: [ repo_list nodes ]
80 Added: | _ ->
81 Added: if nodes = [] then []
82 Added: else [ h3 [] [ txt "Repositories" ]; repo_list nodes ]))
64 83 in
84 Added: let archived_section =
85 Added: match archived with
86 Added: | [] -> HTML.null []
87 Added: | _ ->
88 Added: HTML.(
89 Added: section
90 Added: [ class_ "repo-section repo-archived" ]
91 Added: [ h3 [] [ txt "Archived" ]; repo_list archived ])
92 Added: in
65 93 let readme_section =
66 94 match readme with
67 95 | None -> HTML.null []
@@ -96,5 +124,6 @@
96 124 repo = None;
97 125 subtitle = "";
98 126 active = Summary;
99 Removed: content = [ tree; readme_section ];
127 Added: content =
128 Added: [ favorites_section; main_section; archived_section; readme_section ];
100 129 }
test/test_config.ml
index db562c60..6fecc3e6 100644..100644
@@ -14,6 +14,8 @@
14 14 title = "";
15 15 host = "127.0.0.1";
16 16 port = 9000;
17 Added: favorite_repositories = [];
18 Added: archived_repositories = [];
17 19 }
18 20 in
19 21 Ogit.Config.write_file ~file (Ogit.Config.to_table config);