package main import "core:fmt" import "core:mem" import "core:slice" import "core:strings" import "core:sync" import "core:time" import libgit "lib:database/git/bindings" import "lib:database/sqlite" import "lib:net/http/router" Repo :: struct { name: string, description: string, head: Repo_Head, } Repo_Head :: struct { author: string, summary: string, updated_at: time.Time, } get_head_info :: proc(soft_serve_path, repo_name: string) -> (Repo_Head, bool) { repo: libgit.Repository repo_path := strings.join({soft_serve_path, "repos", repo_name}, "/") defer delete(repo_path) repo_path_cstr := strings.clone_to_cstring(repo_path) defer delete(repo_path_cstr) result := libgit.repository_open_bare(&repo, repo_path_cstr) if result < .Ok { err := libgit.error_last() fmt.eprintf("git error: %d -> %s, %d\n", result, err.message, err.klass) return {}, false } defer libgit.repository_free(repo) head_ref: libgit.Reference result = libgit.repository_head(&head_ref, repo) if result < .Ok { err := libgit.error_last() fmt.eprintf("git error: %d -> %s, %d\n", result, err.message, err.klass) return {}, false } defer libgit.reference_free(head_ref) head_obj: libgit.Object result = libgit.reference_peel(&head_obj, head_ref, .Commit) if result < .Ok { err := libgit.error_last() fmt.eprintf("git error: %d -> %s, %d\n", result, err.message, err.klass) return {}, false } defer libgit.object_free(head_obj) commit := libgit.Commit(head_obj) // oid := libgit.commit_id(commit) signature := libgit.commit_author(commit) return { author = strings.clone_from(signature.name), summary = strings.clone_from(libgit.commit_summary(commit)), updated_at = time.Time{_nsec = signature.sig_when.time * i64(time.Second)}, }, true } get_repos :: proc(repos: ^[dynamic]Repo, soft_serve_path, user_filter: string) -> bool { libgit.libgit2_init() defer libgit.libgit2_shutdown() db_path := strings.join({soft_serve_path, "soft-serve.db"}, "/") defer delete(db_path) db := sqlite.open(db_path) or_return defer sqlite.close(db) user_id: int if user_filter != "" { // users // id|username|admin|password|created_at|updated_at // 1|admin|1||2026-01-15 12:34:08|2026-01-15 12:34:08 // 2|perlw|0||2026-01-15 12:47:02|2026-01-15 12:47:02 rows := sqlite.query(db, "SELECT id FROM users WHERE username = ?", user_filter) or_return defer sqlite.rows_close(rows) if sqlite.rows_next(rows) { sqlite.rows_scan(rows, &user_id) } } // repos // id|name|project_name|description|private|mirror|hidden|user_id|created_at|updated_at // 1|valkyr||Go-powered proxy|0|0|0|2|2026-01-15 14:23:52|2026-01-15 14:23:52 rows: ^sqlite.Rows if user_id > 0 { rows = sqlite.query( db, "SELECT name, description FROM repos WHERE user_id = ? AND private = 0 AND hidden = 0", user_id, ) or_return } else { rows = sqlite.query(db, "SELECT name, description FROM repos WHERE private = 0 AND hidden = 0") or_return } defer sqlite.rows_close(rows) for sqlite.rows_next(rows) { name, description: string sqlite.rows_scan(rows, &name, &description) git_name := strings.join({name, ".git"}, "") defer delete(git_name) head := get_head_info(soft_serve_path, git_name) or_return append(repos, Repo{name = name, description = description, head = head}) } return true } Repo_Map :: struct { repos: [dynamic]Repo, soft_serve_path: string, user_filter: string, updated_at: time.Time, arena: mem.Arena, update_mutex: sync.RW_Mutex, } repo_map_init :: proc(repo_map: ^Repo_Map, soft_serve_path, user_filter: string) { mem.arena_init(&repo_map.arena, make([]u8, 10485760)) context.allocator = mem.arena_allocator(&repo_map.arena) repo_map.repos = make([dynamic]Repo, 0, 10) repo_map.soft_serve_path = soft_serve_path repo_map.user_filter = user_filter } repo_map_destroy :: proc(repo_map: ^Repo_Map) { context.allocator = mem.arena_allocator(&repo_map.arena) repo_map_clear(repo_map) delete(repo_map.repos) delete(repo_map.arena.data) } repo_map_clear :: proc(repo_map: ^Repo_Map) { context.allocator = mem.arena_allocator(&repo_map.arena) for repo in repo_map.repos { delete(repo.name) delete(repo.description) delete(repo.head.author) delete(repo.head.summary) } clear(&repo_map.repos) } repo_map_update :: proc(repo_map: ^Repo_Map) { context.allocator = mem.arena_allocator(&repo_map.arena) if time.diff(repo_map.updated_at, time.now()) > time.Minute { sync.lock(&repo_map.update_mutex) repo_map_clear(repo_map) if !get_repos(&repo_map.repos, repo_map.soft_serve_path, repo_map.user_filter) { fmt.eprintf("get_repos: failed refreshing repo_map") } else { fmt.printf("#!# refreshed repo map #!#\n") } repo_map.updated_at = time.now() sync.unlock(&repo_map.update_mutex) } } repo_map_get_repo :: proc(repo_map: ^Repo_Map, repo_name: string) -> (Repo, bool) { for repo in repo_map.repos { if repo.name == repo_name { return repo, true } } return {}, false } repo_map_updater :: proc( req: router.Request, res: ^router.Response, ctx: rawptr, next: router.Route_Handler, next_ctx: rawptr, ) { repo_map := (^Repo_Map)(ctx) repo_map_update(repo_map) sync.shared_lock(&repo_map.update_mutex) next(req, res, next_ctx) defer sync.shared_unlock(&repo_map.update_mutex) } Entry_Type :: enum { File, Dir, Other, } Repo_Tree_Entry :: struct { type: Entry_Type, name: string, } get_repo_tree :: proc( repo_map: ^Repo_Map, repo_name: string, ref_name := "", base_path := "", ) -> ( []Repo_Tree_Entry, bool, ) { libgit.libgit2_init() defer libgit.libgit2_shutdown() repo: libgit.Repository repo_name_postfixed := strings.join({repo_name, ".git"}, "") defer delete(repo_name_postfixed) repo_path := strings.join({repo_map.soft_serve_path, "repos", repo_name_postfixed}, "/") defer delete(repo_path) repo_path_cstr := strings.clone_to_cstring(repo_path) defer delete(repo_path_cstr) result := libgit.repository_open_bare(&repo, repo_path_cstr) if result < .Ok { err := libgit.error_last() fmt.eprintf("git error: %d -> %s, %d\n", result, err.message, err.klass) return nil, false } defer libgit.repository_free(repo) head_obj: libgit.Object if ref_name == "" { ref: libgit.Reference result = libgit.repository_head(&ref, repo) if result < .Ok { when ODIN_DEBUG { err := libgit.error_last() fmt.eprintf("git error: %d -> %s, %d\n", result, err.message, err.klass) } return nil, false } defer libgit.reference_free(ref) result = libgit.reference_peel(&head_obj, ref, .Commit) if result < .Ok { when ODIN_DEBUG { err := libgit.error_last() fmt.eprintf("git error: %d -> %s, %d\n", result, err.message, err.klass) } return nil, false } } else if len(ref_name) > 4 && ref_name[0:4] == "refs" { ref: libgit.Reference ref_name_cstr := strings.clone_to_cstring(ref_name) defer delete(ref_name_cstr) result = libgit.reference_lookup(&ref, repo, ref_name_cstr) if result < .Ok { when ODIN_DEBUG { err := libgit.error_last() fmt.eprintf("git error: %d -> %s, %d\n", result, err.message, err.klass) } return nil, false } defer libgit.reference_free(ref) result = libgit.reference_peel(&head_obj, ref, .Commit) if result < .Ok { when ODIN_DEBUG { err := libgit.error_last() fmt.eprintf("git error: %d -> %s, %d\n", result, err.message, err.klass) } return nil, false } } else { hash_cstr := strings.clone_to_cstring(ref_name) defer delete(hash_cstr) result = libgit.revparse_single(&head_obj, repo, hash_cstr) if result < .Ok { when ODIN_DEBUG { err := libgit.error_last() fmt.eprintf("git error: %d -> %s, %d\n", result, err.message, err.klass) } return nil, false } } defer libgit.object_free(head_obj) tree: libgit.Tree result = libgit.commit_tree(&tree, libgit.Commit(head_obj)) if result < .Ok { err := libgit.error_last() fmt.eprintf("git error: %d -> %s, %d\n", result, err.message, err.klass) return nil, false } defer libgit.tree_free(tree) sub_tree: libgit.Tree if base_path != "" { base_entry: libgit.Tree_Entry base_path_cstr := strings.clone_to_cstring(base_path) defer delete(base_path_cstr) result = libgit.tree_entry_bypath(&base_entry, tree, base_path_cstr) if result < .Ok { err := libgit.error_last() fmt.eprintf("git error: %d -> %s, %d\n", result, err.message, err.klass) return nil, false } defer libgit.tree_entry_free(base_entry) if libgit.tree_entry_type(base_entry) != .Tree { fmt.eprintf("git error: not a tree -> %s\n", base_path) return nil, false } result = libgit.tree_lookup(&sub_tree, repo, libgit.tree_entry_id(base_entry)) if result < .Ok { err := libgit.error_last() fmt.eprintf("git error: %d -> %s, %d\n", result, err.message, err.klass) return nil, false } } defer if sub_tree != nil { libgit.tree_free(sub_tree) } search_tree := sub_tree if sub_tree != nil else tree count := libgit.tree_entrycount(search_tree) repo_tree_list := make([]Repo_Tree_Entry, count) for i in 0 ..< count { entry := libgit.tree_entry_byindex(search_tree, i) name := libgit.tree_entry_name(entry) type := libgit.tree_entry_type(entry) #partial switch type { case .Tree: repo_tree_list[i].type = .Dir case .Blob: repo_tree_list[i].type = .File case: repo_tree_list[i].type = .Other } repo_tree_list[i].name = strings.clone_from_cstring(name) } return repo_tree_list, true } get_raw_blob :: proc(repo_map: ^Repo_Map, repo_name: string, ref_name := "", filename := "") -> ([]u8, bool) { libgit.libgit2_init() defer libgit.libgit2_shutdown() repo: libgit.Repository repo_name_postfixed := strings.join({repo_name, ".git"}, "") defer delete(repo_name_postfixed) repo_path := strings.join({repo_map.soft_serve_path, "repos", repo_name_postfixed}, "/") defer delete(repo_path) repo_path_cstr := strings.clone_to_cstring(repo_path) defer delete(repo_path_cstr) result := libgit.repository_open_bare(&repo, repo_path_cstr) if result < .Ok { err := libgit.error_last() fmt.eprintf("git error: %d -> %s, %d\n", result, err.message, err.klass) return nil, false } defer libgit.repository_free(repo) ref: libgit.Reference if ref_name == "" { result = libgit.repository_head(&ref, repo) } else { ref_name_cstr := strings.clone_to_cstring(ref_name) defer delete(ref_name_cstr) result = libgit.reference_lookup(&ref, repo, ref_name_cstr) } if result < .Ok { err := libgit.error_last() fmt.eprintf("git error: %d -> %s, %d\n", result, err.message, err.klass) return nil, false } defer libgit.reference_free(ref) head_obj: libgit.Object result = libgit.reference_peel(&head_obj, ref, .Commit) if result < .Ok { err := libgit.error_last() fmt.eprintf("git error: %d -> %s, %d\n", result, err.message, err.klass) return nil, false } defer libgit.object_free(head_obj) commit := libgit.Commit(head_obj) tree: libgit.Tree result = libgit.commit_tree(&tree, commit) if result < .Ok { err := libgit.error_last() fmt.eprintf("git error: %d -> %s, %d\n", result, err.message, err.klass) return nil, false } defer libgit.tree_free(tree) entry: libgit.Tree_Entry filename_cstr := strings.clone_to_cstring(filename) defer delete(filename_cstr) result = libgit.tree_entry_bypath(&entry, tree, filename_cstr) if result < .Ok { err := libgit.error_last() fmt.eprintf("git error: %d -> %s, %d\n", result, err.message, err.klass) return nil, false } defer libgit.tree_entry_free(entry) if libgit.tree_entry_type(entry) != .Blob { fmt.eprintf("git error: not a blob -> %s\n", filename) return nil, false } blob: libgit.Blob result = libgit.blob_lookup(&blob, repo, libgit.tree_entry_id(entry)) if result < .Ok { err := libgit.error_last() fmt.eprintf("git error: %d -> %s, %d\n", result, err.message, err.klass) return nil, false } defer libgit.blob_free(blob) raw_blob := libgit.blob_rawcontent(blob) size := libgit.blob_rawsize(blob) return slice.clone(slice.bytes_from_ptr(raw_blob, int(size))), true }