diff options
author | Marius Peter <marius.peter@tutanota.com> | 2025-05-01 19:28:20 +0200 |
---|---|---|
committer | Marius Peter <marius.peter@tutanota.com> | 2025-05-01 19:28:20 +0200 |
commit | f94d27e3df990d9dd7dae69191dd05e5f691b0bb (patch) | |
tree | 0306d92ed67b1b9ad10aa1f7926a25daea119ee5 | |
parent | ad02d028ab875c5d5ba90f3f8bed31f2678ed664 (diff) |
Perform deep refactoring.
Better separation of concern; this paves the way for better testing
down the road.
-rw-r--r-- | lib/git_helpers.ml | 34 | ||||
-rw-r--r-- | lib/git_unhelpers.ml | 46 | ||||
-rw-r--r-- | lib/handlers.ml | 37 | ||||
-rw-r--r-- | lib/views.ml | 197 |
4 files changed, 112 insertions, 202 deletions
diff --git a/lib/git_helpers.ml b/lib/git_helpers.ml index 0626d02..4e2215e 100644 --- a/lib/git_helpers.ml +++ b/lib/git_helpers.ml @@ -1,11 +1,39 @@ open Lwt.Infix -let get_head_commit_hash repo_path = - let full_path = Filename.concat Config.git_directory repo_path in - let%lwt store_result = Git_unix.Store.v @@ Fpath.v full_path in +let full_path path = Filename.concat Config.git_directory path + +let head_commit_hash repo_path = + let%lwt store_result = Git_unix.Store.v @@ Fpath.v @@ full_path repo_path in match store_result with | Error _ -> Lwt.return_error "Could not open the Git repository." | Ok store -> ( Git_unix.Store.Ref.resolve store Git.Reference.head >|= function | Error _ -> Error ("Failed to resolve HEAD for repo " ^ repo_path) | Ok hash -> Ok (Git_unix.Store.Hash.to_hex hash)) + +let latest_commits repo_path count = + let cmd = + Printf.sprintf "git -C %s log --pretty=format:'%%ad %%s' --date=short -n %d" + (full_path repo_path) count + in + Lwt.catch + (fun () -> + let%lwt output = Lwt_process.pread ("", [| "sh"; "-c"; cmd |]) in + let lines = String.split_on_char '\n' output in + Lwt.return_ok lines) + (fun exn -> Lwt.return_error (Printexc.to_string exn)) + +let all_branches repo_path = + let cmd = + Printf.sprintf "git -C %s branch --format=%%(refname:short)" + @@ full_path repo_path + in + Lwt.catch + (fun () -> + let%lwt output = Lwt_process.pread ("", [| "sh"; "-c"; cmd |]) in + let branches = + String.split_on_char '\n' output + |> List.filter (fun s -> String.trim s <> "") + in + Lwt.return_ok branches) + (fun exn -> Lwt.return_error (Printexc.to_string exn)) diff --git a/lib/git_unhelpers.ml b/lib/git_unhelpers.ml deleted file mode 100644 index 5b55c16..0000000 --- a/lib/git_unhelpers.ml +++ /dev/null @@ -1,46 +0,0 @@ -(* These will be reimplemented using OCaml's Git library, one day... *) - -let get_latest_commits repo_path count = - let open Printf in - let full_path = Filename.concat Config.git_directory repo_path in - let full_cmd = - let command = sprintf "git -C %s log" full_path in - let options = - let format = "--pretty=format:'%ad %s'" in - let date = "--date=short" in - let count = sprintf "-n %s" (string_of_int count) in - [ format; date; count ] - in - String.concat " " (command :: options) - in - let ic = Unix.open_process_in full_cmd in - let rec read_lines acc = - try - let line = input_line ic in - read_lines (line :: acc) - with End_of_file -> - ignore (Unix.close_process_in ic); - List.rev acc - in - read_lines [] - -let get_all_branches repo_path = - let open Printf in - let full_path = Filename.concat Config.git_directory repo_path in - let full_cmd = sprintf "git -C %s branch" full_path in - let ic = Unix.open_process_in full_cmd in - let rec read_lines acc = - try - let line = input_line ic |> String.trim in - let clean_line = - if String.length line > 2 && String.sub line 0 2 = "* " then - String.sub line 2 (String.length line - 2) - (* Remove "* " from active branch *) - else line - in - read_lines (clean_line :: acc) - with End_of_file -> - ignore (Unix.close_process_in ic); - List.rev acc - in - read_lines [] diff --git a/lib/handlers.ml b/lib/handlers.ml index 171cef2..27ca0b6 100644 --- a/lib/handlers.ml +++ b/lib/handlers.ml @@ -1,27 +1,34 @@ -let ogit_root _req = Views.Ogit_root.render () |> Dream_html.respond +let ogit_root _req = Views.ogit_root () |> Dream_html.respond -let repo_root req = - let repo_name = Dream.param req "repo_name" in - Views.Repo_root.render repo_name |> Dream_html.respond +let repo_summary req = + let repo_path = Dream.param req "repo_name" in + let%lwt branches_result = Git_helpers.all_branches repo_path in + let%lwt commits_result = Git_helpers.latest_commits repo_path 10 in + match (branches_result, commits_result) with + | Ok branches, Ok commits -> + Views.repo_summary repo_path ~branches ~commits |> Dream_html.respond + | Error msg, _ | _, Error msg -> Views.error_page msg |> Dream_html.respond -let repo_tree req = - let repo_name = Dream.param req "repo_name" in - let dir_path = Dream.target req in - Views.Repo_tree.render repo_name dir_path |> Dream_html.respond +(* let repo_tree req = *) +(* let repo_name = Dream.param req "repo_name" in *) +(* let path = Git_helpers.full_path repo_name in *) +(* let dir_path = Dream.target req in *) +(* Views.repo_tree ~repo_path:path dir_path |> Dream_html.respond *) -let repo_blob req = - let repo_name = Dream.param req "repo_name" in - let blob_path = Dream.query req "path" |> Option.value ~default:"" in - Views.Repo_blob.render repo_name blob_path |> Dream_html.respond +(* let repo_blob req = *) +(* let repo_name = Dream.param req "repo_name" in *) +(* let path = Git_helpers.full_path repo_name in *) +(* let blob_path = Dream.query req "path" |> Option.value ~default:"" in *) +(* Views.repo_blob repo_name blob_path |> Dream_html.respond *) let all_handlers = [ Dream.get "/" ogit_root; Dream.scope "/:repo_name" [] [ - Dream.get "/" repo_root; - Dream.get "/tree" repo_tree; - Dream.get "/blob" repo_blob; + Dream.get "/" repo_summary; + (* Dream.get "/tree" repo_tree; *) + (* Dream.get "/blob" repo_blob; *) ]; Dream.get "/static/**" (Dream.static "./lib/static"); ] diff --git a/lib/views.ml b/lib/views.ml index bbb9de5..a362e40 100644 --- a/lib/views.ml +++ b/lib/views.ml @@ -42,11 +42,11 @@ module Layout = struct ] end -module Topnav = struct +module Components = struct open Dream_html open HTML - let repo repo_path current_path = + let topnav repo_path current_path = let li_of_a (path, text) = let is_active = path = current_path in let attrs = if is_active then [ id "active" ] else [] in @@ -72,19 +72,19 @@ module Topnav = struct ] end -module Ogit_root = struct - open Dream_html - open HTML - +let ogit_root () = + let open Dream_html in + let open HTML in let repositories_in directory = try let repos = Sys.readdir directory |> Array.to_list |> List.sort String.compare in - let li_of_repo repo = li [] [ a [ href "%s" repo ] [ txt "%s" repo ] ] in + let li_of_repo repo = li [] [ a [ href "%s/" repo ] [ txt "%s" repo ] ] in div [ id "repositories" ] [ ul [] @@ List.map li_of_repo repos ] with Sys_error _ -> div [] [ txt "Error: Unable to read repository list." ] + in let body_data = { @@ -93,134 +93,55 @@ module Ogit_root = struct topnav = null []; content = [ repositories_in Config.git_directory ]; } - - let render () = Layout.application body_data -end - -module Repo_root = struct - open Dream_html - open HTML - - let branches = - let repo_path = Fpath.v "/home/blendux/git.test/ogit.git/" in - let load_repo () = Git_unix.Store.v ~dotgit:repo_path repo_path in - let store = Lwt_main.run @@ load_repo () in - let refs = Lwt_main.run @@ Git_unix.Store.Ref.list @@ Result.get_ok store in - refs |> List.map fst |> List.map Git.Reference.to_string - - let render repo_path = - let title = repo_path in - let subtitle = Filename.concat Config.git_directory repo_path in - let li_of_branch hash = li [] [ txt "%s" hash ] in - let recent_commits = Git_unhelpers.get_latest_commits repo_path 10 in - let li_of_commit commit = - li [] [ a [ href "%s" commit ] [ txt "%s" commit ] ] - in - let content = - [ - h3 [] [ txt "Branches" ]; - ul [] (List.map li_of_branch branches); - h3 [] [ txt "Recent commits" ]; - ul [] (List.map li_of_commit recent_commits); - ] - in - let body_data = - { title; subtitle; topnav = Topnav.repo repo_path ""; content } - in - Layout.application body_data -end - -module Repo_tree = struct - open Dream_html - open HTML - - let full_path repo_name dir_path = - Filename.concat (Filename.concat Config.git_directory repo_name) dir_path - - (* Helper function to list contents of a given directory *) - let ls_dir repo_name dir_path = - let dir_full_path = full_path repo_name dir_path in - try - Sys.readdir dir_full_path |> Array.to_list - |> List.filter (fun name -> name <> ".git") (* Exclude .git *) - |> List.map (fun entry -> - let entry_rel_path = Filename.concat dir_path entry in - let full_entry_path = Filename.concat dir_full_path entry in - (entry, entry_rel_path, Sys.is_directory full_entry_path)) - |> List.sort (fun (a, _, is_dir_a) (b, _, is_dir_b) -> - match (is_dir_a, is_dir_b) with - | true, false -> -1 (* Directories first *) - | false, true -> 1 - | _ -> String.compare a b) - with Sys_error _ -> [] - - (* Function to create a link based on file type *) - let link_for_entry repo_name (entry, entry_rel_path, is_dir) = - let link = - if is_dir then Printf.sprintf "/%s/tree?path=%s" repo_name entry_rel_path - else Printf.sprintf "/%s/blob?path=%s" repo_name entry_rel_path - in - let display_name = if is_dir then entry ^ "/" else entry in - li [] [ a [ href "%s" link ] [ txt "%s" display_name ] ] - - (* Render function *) - let render repo_name dir_path = - let title = repo_name in - let subtitle = "Files" in - - let repo_entries = ls_dir repo_name @@ dir_path in - let content = - [ - txt "%s" dir_path; - ul [] (List.map (link_for_entry repo_name) repo_entries); - ] - in - - let body_data = - { title; subtitle; topnav = Topnav.repo repo_name "tree"; content } - in - Layout.application body_data -end - -module Repo_blob = struct - open Dream_html - open HTML - - let full_path repo_path = Filename.concat Config.git_directory repo_path - - (* Read the contents of a file *) - let read_blob repo_path blob_name = - let file_path = Filename.concat (full_path repo_path) blob_name in - try Some (In_channel.with_open_text file_path In_channel.input_all) - with _ -> - None (* Handle cases where the file doesn't exist or can't be read *) - - (* Render function *) - let render repo_path blob_name = - let title = blob_name in - let subtitle = "File Contents" in - - match read_blob repo_path blob_name with - | Some content -> - let content_display = pre [] [ code [] [ txt "%s" content ] ] in - let body_data = - { - title; - subtitle; - topnav = Topnav.repo repo_path "blob"; - content = [ content_display ]; - } - in - Layout.application body_data - | None -> - let error_message = p [] [ txt "Error: Unable to read file." ] in - let body_data = - { - title; - subtitle; - topnav = Topnav.repo repo_path "blob"; - content = [ error_message ]; - } - in - Layout.application body_data -end + in + Layout.application body_data + +let repo_summary repo_path ~branches ~commits = + let open Dream_html in + let open HTML in + let li_of_branch branch = + li [] [ a [ href "%s" branch ] [ txt "%s" branch ] ] + in + let li_of_commit commit = + li [] [ a [ href "%s" commit ] [ txt "%s" commit ] ] + in + let content = + [ + h3 [] [ txt "Branches" ]; + ul [] (List.map li_of_branch branches); + h3 [] [ txt "Recent commits" ]; + ul [] (List.map li_of_commit commits); + ] + in + let body_data = + { + title = repo_path; + subtitle = "Macaroniii"; + topnav = Components.topnav repo_path ""; + content; + } + in + Layout.application body_data + +let error_page message = + let open Dream_html in + let open HTML in + html [] + [ + head [] + [ + title [] "Big error"; + link [ rel "stylesheet"; href "/static/styles.css" ]; + link [ rel "icon"; type_ "image/x-icon"; href "/static/git_icon.svg" ]; + ]; + body [] + [ + h1 [] [ txt "Major error alert" ]; + h2 [] [ txt "Major alert subtitle" ]; + (* Components.topnav; *) + div [ id "main" ] [ txt "%s" message ]; + ]; + ] + +let repo_tree repo_path = repo_summary repo_path +let repo_blob repo_path = repo_summary repo_path |