Perform deep refactoring.

Better separation of concern; this paves the way for better testing down the road.

Commit
f94d27e3df990d9dd7dae69191dd05e5f691b0bb
Author
Marius Peter <marius.peter@tutanota.com>
Author date
Committer
Marius Peter <marius.peter@tutanota.com>
Committer date
Changed files
lib/git_helpers.ml
index 0626d028..4e2215e8 100644..100644
@@ -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
index 5b55c168..00000000 100644..000000
@@ -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
index 171cef21..27ca0b68 100644..100644
@@ -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
index bbb9de51..a362e409 100644..100644
@@ -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