[OCaml] Mobile-friendly clone of cgit.
Great work today! 🔥
Changed files
bin/main.ml
@@ -6,5 +6,8 @@
6
6
Dream.get "/:repo_name" (fun req ->
7
7
let repo_name = Dream.param req "repo_name" in
8
8
Ogit.Handlers.repo_root repo_name);
9
Added:
Dream.get "/:repo_name/tree" (fun req ->
10
Added:
let repo_name = Dream.param req "repo_name" in
11
Added:
Ogit.Handlers.repo_tree repo_name);
9
12
Dream.get "/static/**" (Dream.static "./lib/static");
10
13
]
lib/git_unhelpers.ml
@@ -1,17 +1,44 @@
1
1
(* These will be reimplemented using OCaml's Git library, one day... *)
2
2
3
Removed:
let get_git_log repo_path =
3
Added:
let get_latest_commits repo_path count =
4
Added:
let open Printf in
4
5
let full_path = Filename.concat Config.git_directory repo_path in
5
6
let full_cmd =
6
Removed:
let cmd = Printf.sprintf "git -C %s log" full_path in
7
Removed:
let options = [ "--pretty=format:'%ad %s'"; "--date=short"; "-n 10" ] in
8
Removed:
String.concat " " (cmd :: options)
7
Added:
let command = sprintf "git -C %s log" full_path in
8
Added:
let options =
9
Added:
let format = "--pretty=format:'%ad %s'" in
10
Added:
let date = "--date=short" in
11
Added:
let count = sprintf "-n %s" (string_of_int count) in
12
Added:
[ format; date; count ]
13
Added:
in
14
Added:
String.concat " " (command :: options)
9
15
in
10
16
let ic = Unix.open_process_in full_cmd in
11
17
let rec read_lines acc =
12
18
try
13
19
let line = input_line ic in
14
20
read_lines (line :: acc)
21
Added:
with End_of_file ->
22
Added:
ignore (Unix.close_process_in ic);
23
Added:
List.rev acc
24
Added:
in
25
Added:
read_lines []
26
Added:
27
Added:
let get_all_branches repo_path =
28
Added:
let open Printf in
29
Added:
let full_path = Filename.concat Config.git_directory repo_path in
30
Added:
let full_cmd = sprintf "git -C %s branch" full_path in
31
Added:
let ic = Unix.open_process_in full_cmd in
32
Added:
let rec read_lines acc =
33
Added:
try
34
Added:
let line = input_line ic |> String.trim in
35
Added:
let clean_line =
36
Added:
if String.length line > 2 && String.sub line 0 2 = "* " then
37
Added:
String.sub line 2 (String.length line - 2)
38
Added:
(* Remove "* " from active branch *)
39
Added:
else line
40
Added:
in
41
Added:
read_lines (clean_line :: acc)
15
42
with End_of_file ->
16
43
ignore (Unix.close_process_in ic);
17
44
List.rev acc
lib/handlers.ml
@@ -1,6 +1,3 @@
1
Removed:
open Lwt.Infix
2
Removed:
3
1
let ogit_root _ = Views.Ogit_root.render () |> Dream_html.respond
4
Removed:
5
Removed:
let repo_root repo_name =
6
Removed:
Views.Repo_root.render repo_name >>= Dream_html.respond
2
Added:
let repo_root repo_name = Views.Repo_root.render repo_name |> Dream_html.respond
3
Added:
let repo_tree repo_name = Views.Repo_tree.render repo_name |> Dream_html.respond
lib/views.ml
@@ -32,7 +32,7 @@
32
32
];
33
33
body []
34
34
[
35
Removed:
header "Ogit" body_data.subtitle;
35
Added:
header body_data.title body_data.subtitle;
36
36
body_data.topnav;
37
37
div [ id "main" ] body_data.content;
38
38
footer;
@@ -40,6 +40,60 @@
40
40
]
41
41
end
42
42
43
Added:
module Topnav = struct
44
Added:
open Dream_html
45
Added:
open HTML
46
Added:
47
Added:
let repo repo_path current_path =
48
Added:
let li_of_a (path, text) =
49
Added:
let is_active = path = current_path in
50
Added:
let attrs = if is_active then [ id "active" ] else [] in
51
Added:
let url =
52
Added:
if String.length path = 0 then Printf.sprintf "/%s" repo_path
53
Added:
else Printf.sprintf "/%s/%s" repo_path path
54
Added:
in
55
Added:
li attrs [ a [ href "%s" url ] [ txt text ] ]
56
Added:
in
57
Added:
nav
58
Added:
[ id "top" ]
59
Added:
[
60
Added:
ul []
61
Added:
@@ List.map li_of_a
62
Added:
[
63
Added:
("", "summary");
64
Added:
("refs", "refs");
65
Added:
("log", "log");
66
Added:
("tree", "tree");
67
Added:
("commit", "commit");
68
Added:
("diff", "diff");
69
Added:
];
70
Added:
]
71
Added:
72
Added:
(* let li_of_a (path, text) = *)
73
Added:
(* let is_active = path = current_path in *)
74
Added:
(* li (if is_active then [ id "active" ] else []) in *)
75
Added:
(* let link_of_path = *)
76
Added:
(* if String.length path = 0 then Printf.sprintf "/%s" repo_path *)
77
Added:
(* else Printf.sprintf "/%s/%s" repo_path path *)
78
Added:
(* in *)
79
Added:
(* li [ id "active" ] [ a [ href "%s" link_of_path ] [ txt text ] ] *)
80
Added:
(* in *)
81
Added:
(* nav *)
82
Added:
(* [ id "top" ] *)
83
Added:
(* [ *)
84
Added:
(* ul [] *)
85
Added:
(* @@ List.map li_of_a *)
86
Added:
(* [ *)
87
Added:
(* ("", "summary"); *)
88
Added:
(* ("refs", "refs"); *)
89
Added:
(* ("log", "log"); *)
90
Added:
(* ("tree", "tree"); *)
91
Added:
(* ("commit", "commit"); *)
92
Added:
(* ("diff", "diff"); *)
93
Added:
(* ]; *)
94
Added:
(* ] *)
95
Added:
end
96
Added:
43
97
module Ogit_root = struct
44
98
open Dream_html
45
99
open HTML
@@ -56,7 +110,7 @@
56
110
57
111
let body_data =
58
112
{
59
Removed:
title = "My repositories";
113
Added:
title = "Ogit";
60
114
subtitle = "Repositories for " ^ Config.author;
61
115
topnav = null [];
62
116
content = [ repositories_in Config.git_directory ];
@@ -68,57 +122,78 @@
68
122
module Repo_root = struct
69
123
open Dream_html
70
124
open HTML
71
Removed:
(* open Lwt.Syntax *)
72
125
126
Added:
(* let href_for_git_object kind identifier = *)
127
Added:
(* let open Printf in *)
128
Added:
(* href "%s" @@ *)
129
Added:
(* match kind with *)
130
Added:
(* | `Branch -> sprintf "/tree/%s" identifier *)
131
Added:
(* | `Commit -> sprintf "/commit/%s" identifier *)
132
Added:
73
133
let render repo_path =
74
Removed:
(* let* title_result = Git_helpers.get_head_commit_hash repo_path in *)
75
Removed:
(* let title = *)
76
Removed:
(* match title_result with Ok hash -> hash | Error msg -> "Error: " ^ msg *)
77
Removed:
(* in *)
78
Removed:
let title = "Finble" in
134
Added:
let title = repo_path in
79
135
let subtitle = Filename.concat Config.git_directory repo_path in
80
Removed:
let topnav =
81
Removed:
nav
82
Removed:
[ id "top" ]
83
Removed:
[
84
Removed:
ul []
85
Removed:
[
86
Removed:
li [] [ a [ href "/" ] [ txt "summary" ] ];
87
Removed:
li [] [ a [ href "/" ] [ txt "refs" ] ];
88
Removed:
li [] [ a [ href "/" ] [ txt "log" ] ];
89
Removed:
li [] [ a [ href "/" ] [ txt "tree" ] ];
90
Removed:
li [] [ a [ href "/" ] [ txt "commit" ] ];
91
Removed:
li [] [ a [ href "/" ] [ txt "diff" ] ];
92
Removed:
];
93
Removed:
]
136
Added:
let all_branches = Git_unhelpers.get_all_branches repo_path in
137
Added:
let li_of_branch branch =
138
Added:
li [] [ a [ href "%s" branch ] [ txt "%s" branch ] ]
94
139
in
95
Removed:
let recent_commits = Git_unhelpers.get_git_log repo_path in
140
Added:
let recent_commits = Git_unhelpers.get_latest_commits repo_path 10 in
96
141
let li_of_commit commit =
97
142
li [] [ a [ href "%s" commit ] [ txt "%s" commit ] ]
98
143
in
99
144
let content =
100
145
[
146
Added:
h3 [] [ txt "Branches" ];
147
Added:
ul [] (List.map li_of_branch all_branches);
101
148
h3 [] [ txt "Recent commits" ];
102
Removed:
ul [] @@ List.map li_of_commit recent_commits;
149
Added:
ul [] (List.map li_of_commit recent_commits);
103
150
]
104
151
in
105
Removed:
let body_data = { title; subtitle; topnav; content } in
106
Removed:
Lwt.return @@ Layout.application body_data
152
Added:
let body_data =
153
Added:
{ title; subtitle; topnav = Topnav.repo repo_path ""; content }
154
Added:
in
155
Added:
Layout.application body_data
107
156
end
108
157
109
158
module Repo_tree = struct
110
159
open Dream_html
111
160
open HTML
112
Removed:
open Lwt.Syntax
113
161
162
Added:
let full_path repo_path = Filename.concat Config.git_directory repo_path
163
Added:
164
Added:
(* Helper function to list top-level files and directories *)
165
Added:
let ls_repo repo_path =
166
Added:
try
167
Added:
Sys.readdir (full_path repo_path)
168
Added:
|> Array.to_list
169
Added:
|> List.filter (fun name -> name <> ".git") (* Exclude .git *)
170
Added:
|> List.sort String.compare
171
Added:
with Sys_error _ -> []
172
Added:
173
Added:
(* Function to create a link based on file type *)
174
Added:
let link_for_entry repo_path entry =
175
Added:
let entry_path = Filename.concat (full_path repo_path) entry in
176
Added:
if Sys.is_directory entry_path then
177
Added:
a
178
Added:
[ href "%s" @@ Printf.sprintf "/%s/tree/%s" repo_path entry ]
179
Added:
[ txt "%s" (entry ^ "/") ]
180
Added:
else
181
Added:
a
182
Added:
[ href "%s" @@ Printf.sprintf "/%s/blob/%s" repo_path entry ]
183
Added:
[ txt "%s" entry ]
184
Added:
185
Added:
let li_of_entry repo_path entry = li [] [ link_for_entry repo_path entry ]
186
Added:
187
Added:
(* Render function *)
114
188
let render repo_path =
115
Removed:
let* title_result = Git_helpers.get_head_commit_hash repo_path in
116
Removed:
let title =
117
Removed:
match title_result with Ok hash -> hash | Error msg -> "Error: " ^ msg
189
Added:
let title = repo_path in
190
Added:
let subtitle = "Files" in
191
Added:
192
Added:
let repo_entries = ls_repo repo_path in
193
Added:
let content = [ ul [] (List.map (li_of_entry repo_path) repo_entries) ] in
194
Added:
195
Added:
let body_data =
196
Added:
{ title; subtitle; topnav = Topnav.repo repo_path "tree"; content }
118
197
in
119
Removed:
let subtitle = "Dinglefops" in
120
Removed:
let topnav = null [] in
121
Removed:
let content = [ txt "foobar" ] in
122
Removed:
let body_data = { title; subtitle; topnav; content } in
123
Removed:
Lwt.return @@ Layout.application body_data
198
Added:
Layout.application body_data
124
199
end