[OCaml] Mobile-friendly clone of cgit.
Perform deep refactoring.
Better separation of concern; this paves the way for better testing down the road.
Changed files
lib/git_helpers.ml
@@ -1,11 +1,39 @@
1
1
open Lwt.Infix
2
2
3
Removed:
let get_head_commit_hash repo_path =
4
Removed:
let full_path = Filename.concat Config.git_directory repo_path in
5
Removed:
let%lwt store_result = Git_unix.Store.v @@ Fpath.v full_path in
3
Added:
let full_path path = Filename.concat Config.git_directory path
4
Added:
5
Added:
let head_commit_hash repo_path =
6
Added:
let%lwt store_result = Git_unix.Store.v @@ Fpath.v @@ full_path repo_path in
6
7
match store_result with
7
8
| Error _ -> Lwt.return_error "Could not open the Git repository."
8
9
| Ok store -> (
9
10
Git_unix.Store.Ref.resolve store Git.Reference.head >|= function
10
11
| Error _ -> Error ("Failed to resolve HEAD for repo " ^ repo_path)
11
12
| Ok hash -> Ok (Git_unix.Store.Hash.to_hex hash))
13
Added:
14
Added:
let latest_commits repo_path count =
15
Added:
let cmd =
16
Added:
Printf.sprintf "git -C %s log --pretty=format:'%%ad %%s' --date=short -n %d"
17
Added:
(full_path repo_path) count
18
Added:
in
19
Added:
Lwt.catch
20
Added:
(fun () ->
21
Added:
let%lwt output = Lwt_process.pread ("", [| "sh"; "-c"; cmd |]) in
22
Added:
let lines = String.split_on_char '\n' output in
23
Added:
Lwt.return_ok lines)
24
Added:
(fun exn -> Lwt.return_error (Printexc.to_string exn))
25
Added:
26
Added:
let all_branches repo_path =
27
Added:
let cmd =
28
Added:
Printf.sprintf "git -C %s branch --format=%%(refname:short)"
29
Added:
@@ full_path repo_path
30
Added:
in
31
Added:
Lwt.catch
32
Added:
(fun () ->
33
Added:
let%lwt output = Lwt_process.pread ("", [| "sh"; "-c"; cmd |]) in
34
Added:
let branches =
35
Added:
String.split_on_char '\n' output
36
Added:
|> List.filter (fun s -> String.trim s <> "")
37
Added:
in
38
Added:
Lwt.return_ok branches)
39
Added:
(fun exn -> Lwt.return_error (Printexc.to_string exn))
lib/git_unhelpers.ml
@@ -1,46 +0,0 @@
1
Removed:
(* These will be reimplemented using OCaml's Git library, one day... *)
2
Removed:
3
Removed:
let get_latest_commits repo_path count =
4
Removed:
let open Printf in
5
Removed:
let full_path = Filename.concat Config.git_directory repo_path in
6
Removed:
let full_cmd =
7
Removed:
let command = sprintf "git -C %s log" full_path in
8
Removed:
let options =
9
Removed:
let format = "--pretty=format:'%ad %s'" in
10
Removed:
let date = "--date=short" in
11
Removed:
let count = sprintf "-n %s" (string_of_int count) in
12
Removed:
[ format; date; count ]
13
Removed:
in
14
Removed:
String.concat " " (command :: options)
15
Removed:
in
16
Removed:
let ic = Unix.open_process_in full_cmd in
17
Removed:
let rec read_lines acc =
18
Removed:
try
19
Removed:
let line = input_line ic in
20
Removed:
read_lines (line :: acc)
21
Removed:
with End_of_file ->
22
Removed:
ignore (Unix.close_process_in ic);
23
Removed:
List.rev acc
24
Removed:
in
25
Removed:
read_lines []
26
Removed:
27
Removed:
let get_all_branches repo_path =
28
Removed:
let open Printf in
29
Removed:
let full_path = Filename.concat Config.git_directory repo_path in
30
Removed:
let full_cmd = sprintf "git -C %s branch" full_path in
31
Removed:
let ic = Unix.open_process_in full_cmd in
32
Removed:
let rec read_lines acc =
33
Removed:
try
34
Removed:
let line = input_line ic |> String.trim in
35
Removed:
let clean_line =
36
Removed:
if String.length line > 2 && String.sub line 0 2 = "* " then
37
Removed:
String.sub line 2 (String.length line - 2)
38
Removed:
(* Remove "* " from active branch *)
39
Removed:
else line
40
Removed:
in
41
Removed:
read_lines (clean_line :: acc)
42
Removed:
with End_of_file ->
43
Removed:
ignore (Unix.close_process_in ic);
44
Removed:
List.rev acc
45
Removed:
in
46
Removed:
read_lines []
lib/handlers.ml
@@ -1,27 +1,34 @@
1
Removed:
let ogit_root _req = Views.Ogit_root.render () |> Dream_html.respond
1
Added:
let ogit_root _req = Views.ogit_root () |> Dream_html.respond
2
2
3
Removed:
let repo_root req =
4
Removed:
let repo_name = Dream.param req "repo_name" in
5
Removed:
Views.Repo_root.render repo_name |> Dream_html.respond
3
Added:
let repo_summary req =
4
Added:
let repo_path = Dream.param req "repo_name" in
5
Added:
let%lwt branches_result = Git_helpers.all_branches repo_path in
6
Added:
let%lwt commits_result = Git_helpers.latest_commits repo_path 10 in
7
Added:
match (branches_result, commits_result) with
8
Added:
| Ok branches, Ok commits ->
9
Added:
Views.repo_summary repo_path ~branches ~commits |> Dream_html.respond
10
Added:
| Error msg, _ | _, Error msg -> Views.error_page msg |> Dream_html.respond
6
11
7
Removed:
let repo_tree req =
8
Removed:
let repo_name = Dream.param req "repo_name" in
9
Removed:
let dir_path = Dream.target req in
10
Removed:
Views.Repo_tree.render repo_name dir_path |> Dream_html.respond
12
Added:
(* let repo_tree req = *)
13
Added:
(* let repo_name = Dream.param req "repo_name" in *)
14
Added:
(* let path = Git_helpers.full_path repo_name in *)
15
Added:
(* let dir_path = Dream.target req in *)
16
Added:
(* Views.repo_tree ~repo_path:path dir_path |> Dream_html.respond *)
11
17
12
Removed:
let repo_blob req =
13
Removed:
let repo_name = Dream.param req "repo_name" in
14
Removed:
let blob_path = Dream.query req "path" |> Option.value ~default:"" in
15
Removed:
Views.Repo_blob.render repo_name blob_path |> Dream_html.respond
18
Added:
(* let repo_blob req = *)
19
Added:
(* let repo_name = Dream.param req "repo_name" in *)
20
Added:
(* let path = Git_helpers.full_path repo_name in *)
21
Added:
(* let blob_path = Dream.query req "path" |> Option.value ~default:"" in *)
22
Added:
(* Views.repo_blob repo_name blob_path |> Dream_html.respond *)
16
23
17
24
let all_handlers =
18
25
[
19
26
Dream.get "/" ogit_root;
20
27
Dream.scope "/:repo_name" []
21
28
[
22
Removed:
Dream.get "/" repo_root;
23
Removed:
Dream.get "/tree" repo_tree;
24
Removed:
Dream.get "/blob" repo_blob;
29
Added:
Dream.get "/" repo_summary;
30
Added:
(* Dream.get "/tree" repo_tree; *)
31
Added:
(* Dream.get "/blob" repo_blob; *)
25
32
];
26
33
Dream.get "/static/**" (Dream.static "./lib/static");
27
34
]
lib/views.ml
@@ -42,11 +42,11 @@
42
42
]
43
43
end
44
44
45
Removed:
module Topnav = struct
45
Added:
module Components = struct
46
46
open Dream_html
47
47
open HTML
48
48
49
Removed:
let repo repo_path current_path =
49
Added:
let topnav repo_path current_path =
50
50
let li_of_a (path, text) =
51
51
let is_active = path = current_path in
52
52
let attrs = if is_active then [ id "active" ] else [] in
@@ -72,19 +72,19 @@
72
72
]
73
73
end
74
74
75
Removed:
module Ogit_root = struct
76
Removed:
open Dream_html
77
Removed:
open HTML
78
Removed:
75
Added:
let ogit_root () =
76
Added:
let open Dream_html in
77
Added:
let open HTML in
79
78
let repositories_in directory =
80
79
try
81
80
let repos =
82
81
Sys.readdir directory |> Array.to_list |> List.sort String.compare
83
82
in
84
Removed:
let li_of_repo repo = li [] [ a [ href "%s" repo ] [ txt "%s" repo ] ] in
83
Added:
let li_of_repo repo = li [] [ a [ href "%s/" repo ] [ txt "%s" repo ] ] in
85
84
div [ id "repositories" ] [ ul [] @@ List.map li_of_repo repos ]
86
85
with Sys_error _ ->
87
86
div [] [ txt "Error: Unable to read repository list." ]
87
Added:
in
88
88
89
89
let body_data =
90
90
{
@@ -93,134 +93,55 @@
93
93
topnav = null [];
94
94
content = [ repositories_in Config.git_directory ];
95
95
}
96
Added:
in
97
Added:
Layout.application body_data
96
98
97
Removed:
let render () = Layout.application body_data
98
Removed:
end
99
Added:
let repo_summary repo_path ~branches ~commits =
100
Added:
let open Dream_html in
101
Added:
let open HTML in
102
Added:
let li_of_branch branch =
103
Added:
li [] [ a [ href "%s" branch ] [ txt "%s" branch ] ]
104
Added:
in
105
Added:
let li_of_commit commit =
106
Added:
li [] [ a [ href "%s" commit ] [ txt "%s" commit ] ]
107
Added:
in
108
Added:
let content =
109
Added:
[
110
Added:
h3 [] [ txt "Branches" ];
111
Added:
ul [] (List.map li_of_branch branches);
112
Added:
h3 [] [ txt "Recent commits" ];
113
Added:
ul [] (List.map li_of_commit commits);
114
Added:
]
115
Added:
in
116
Added:
let body_data =
117
Added:
{
118
Added:
title = repo_path;
119
Added:
subtitle = "Macaroniii";
120
Added:
topnav = Components.topnav repo_path "";
121
Added:
content;
122
Added:
}
123
Added:
in
124
Added:
Layout.application body_data
99
125
100
Removed:
module Repo_root = struct
101
Removed:
open Dream_html
102
Removed:
open HTML
126
Added:
let error_page message =
127
Added:
let open Dream_html in
128
Added:
let open HTML in
129
Added:
html []
130
Added:
[
131
Added:
head []
132
Added:
[
133
Added:
title [] "Big error";
134
Added:
link [ rel "stylesheet"; href "/static/styles.css" ];
135
Added:
link [ rel "icon"; type_ "image/x-icon"; href "/static/git_icon.svg" ];
136
Added:
];
137
Added:
body []
138
Added:
[
139
Added:
h1 [] [ txt "Major error alert" ];
140
Added:
h2 [] [ txt "Major alert subtitle" ];
141
Added:
(* Components.topnav; *)
142
Added:
div [ id "main" ] [ txt "%s" message ];
143
Added:
];
144
Added:
]
103
145
104
Removed:
let branches =
105
Removed:
let repo_path = Fpath.v "/home/blendux/git.test/ogit.git/" in
106
Removed:
let load_repo () = Git_unix.Store.v ~dotgit:repo_path repo_path in
107
Removed:
let store = Lwt_main.run @@ load_repo () in
108
Removed:
let refs = Lwt_main.run @@ Git_unix.Store.Ref.list @@ Result.get_ok store in
109
Removed:
refs |> List.map fst |> List.map Git.Reference.to_string
110
Removed:
111
Removed:
let render repo_path =
112
Removed:
let title = repo_path in
113
Removed:
let subtitle = Filename.concat Config.git_directory repo_path in
114
Removed:
let li_of_branch hash = li [] [ txt "%s" hash ] in
115
Removed:
let recent_commits = Git_unhelpers.get_latest_commits repo_path 10 in
116
Removed:
let li_of_commit commit =
117
Removed:
li [] [ a [ href "%s" commit ] [ txt "%s" commit ] ]
118
Removed:
in
119
Removed:
let content =
120
Removed:
[
121
Removed:
h3 [] [ txt "Branches" ];
122
Removed:
ul [] (List.map li_of_branch branches);
123
Removed:
h3 [] [ txt "Recent commits" ];
124
Removed:
ul [] (List.map li_of_commit recent_commits);
125
Removed:
]
126
Removed:
in
127
Removed:
let body_data =
128
Removed:
{ title; subtitle; topnav = Topnav.repo repo_path ""; content }
129
Removed:
in
130
Removed:
Layout.application body_data
131
Removed:
end
132
Removed:
133
Removed:
module Repo_tree = struct
134
Removed:
open Dream_html
135
Removed:
open HTML
136
Removed:
137
Removed:
let full_path repo_name dir_path =
138
Removed:
Filename.concat (Filename.concat Config.git_directory repo_name) dir_path
139
Removed:
140
Removed:
(* Helper function to list contents of a given directory *)
141
Removed:
let ls_dir repo_name dir_path =
142
Removed:
let dir_full_path = full_path repo_name dir_path in
143
Removed:
try
144
Removed:
Sys.readdir dir_full_path |> Array.to_list
145
Removed:
|> List.filter (fun name -> name <> ".git") (* Exclude .git *)
146
Removed:
|> List.map (fun entry ->
147
Removed:
let entry_rel_path = Filename.concat dir_path entry in
148
Removed:
let full_entry_path = Filename.concat dir_full_path entry in
149
Removed:
(entry, entry_rel_path, Sys.is_directory full_entry_path))
150
Removed:
|> List.sort (fun (a, _, is_dir_a) (b, _, is_dir_b) ->
151
Removed:
match (is_dir_a, is_dir_b) with
152
Removed:
| true, false -> -1 (* Directories first *)
153
Removed:
| false, true -> 1
154
Removed:
| _ -> String.compare a b)
155
Removed:
with Sys_error _ -> []
156
Removed:
157
Removed:
(* Function to create a link based on file type *)
158
Removed:
let link_for_entry repo_name (entry, entry_rel_path, is_dir) =
159
Removed:
let link =
160
Removed:
if is_dir then Printf.sprintf "/%s/tree?path=%s" repo_name entry_rel_path
161
Removed:
else Printf.sprintf "/%s/blob?path=%s" repo_name entry_rel_path
162
Removed:
in
163
Removed:
let display_name = if is_dir then entry ^ "/" else entry in
164
Removed:
li [] [ a [ href "%s" link ] [ txt "%s" display_name ] ]
165
Removed:
166
Removed:
(* Render function *)
167
Removed:
let render repo_name dir_path =
168
Removed:
let title = repo_name in
169
Removed:
let subtitle = "Files" in
170
Removed:
171
Removed:
let repo_entries = ls_dir repo_name @@ dir_path in
172
Removed:
let content =
173
Removed:
[
174
Removed:
txt "%s" dir_path;
175
Removed:
ul [] (List.map (link_for_entry repo_name) repo_entries);
176
Removed:
]
177
Removed:
in
178
Removed:
179
Removed:
let body_data =
180
Removed:
{ title; subtitle; topnav = Topnav.repo repo_name "tree"; content }
181
Removed:
in
182
Removed:
Layout.application body_data
183
Removed:
end
184
Removed:
185
Removed:
module Repo_blob = struct
186
Removed:
open Dream_html
187
Removed:
open HTML
188
Removed:
189
Removed:
let full_path repo_path = Filename.concat Config.git_directory repo_path
190
Removed:
191
Removed:
(* Read the contents of a file *)
192
Removed:
let read_blob repo_path blob_name =
193
Removed:
let file_path = Filename.concat (full_path repo_path) blob_name in
194
Removed:
try Some (In_channel.with_open_text file_path In_channel.input_all)
195
Removed:
with _ ->
196
Removed:
None (* Handle cases where the file doesn't exist or can't be read *)
197
Removed:
198
Removed:
(* Render function *)
199
Removed:
let render repo_path blob_name =
200
Removed:
let title = blob_name in
201
Removed:
let subtitle = "File Contents" in
202
Removed:
203
Removed:
match read_blob repo_path blob_name with
204
Removed:
| Some content ->
205
Removed:
let content_display = pre [] [ code [] [ txt "%s" content ] ] in
206
Removed:
let body_data =
207
Removed:
{
208
Removed:
title;
209
Removed:
subtitle;
210
Removed:
topnav = Topnav.repo repo_path "blob";
211
Removed:
content = [ content_display ];
212
Removed:
}
213
Removed:
in
214
Removed:
Layout.application body_data
215
Removed:
| None ->
216
Removed:
let error_message = p [] [ txt "Error: Unable to read file." ] in
217
Removed:
let body_data =
218
Removed:
{
219
Removed:
title;
220
Removed:
subtitle;
221
Removed:
topnav = Topnav.repo repo_path "blob";
222
Removed:
content = [ error_message ];
223
Removed:
}
224
Removed:
in
225
Removed:
Layout.application body_data
226
Removed:
end
146
Added:
let repo_tree repo_path = repo_summary repo_path
147
Added:
let repo_blob repo_path = repo_summary repo_path