(** Git data access layer. *) module Store = Git_unix.Store type error = | Bad_request of string | Not_found of string | Store_error of Store.error | Internal of string val pp_error : Format.formatter -> error -> unit (** {1 Validation} *) val is_valid_hash_hex : string -> bool val is_valid_repo_name : string -> bool (** {1 Repository discovery and context} *) type repository_layout = { worktree : string; git_dir : string } type repository val repositories : Config.t -> (string list, error) result type repo_info = { repo_name : string; description : string } type fs_node = Repo of repo_info | Directory of string * fs_node list val scan_project_root : Config.t -> (fs_node list, error) result val open_repository : Config.t -> string -> (repository, error) Lwt_result.t val repository_name : repository -> string val repository_description : repository -> string val close_repository : repository -> unit Lwt.t val head_commit_date : repository -> (int64 * Git.User.tz_offset option) option Lwt.t val head_hash_hint : Config.t -> string -> string option (** Cheaply resolve the commit hash that HEAD names, using plain file reads and no object store. [None] means "could not tell cheaply" — fall back to opening the repository rather than conclude anything. Intended as a cache key: any new commit on the served branch changes the result. *) (** {1 Repository metadata} *) val default_repo_description : string val short_hash : string -> string (** {1 Commits} *) module Commit : sig type user = Git.User.t type t = { hash : string; tree : string; parents : string list; author : user; committer : user; message : string option; } val of_id : repository -> string -> (t, error) Lwt_result.t val recent_from : repository -> string -> int -> (t list, error) Lwt_result.t val recent_matching : ?max_examined:int -> repository -> int -> (t -> bool) -> (t list * bool, error) Lwt_result.t (** Walk history from HEAD, collecting up to [count] commits that satisfy the predicate. The walk reads at most [max_examined] commits (default 5000), so a rare predicate cannot traverse an entire large history per request. The boolean is [true] when the walk stopped at that cap with history left unexplored — older matches may then exist beyond the result. *) end (** {1 References} *) module Reference : sig type t = { name : string; hash : string } val branch_name : string -> string option val tag_name : string -> string option val of_id : repository -> string -> (t, error) Lwt_result.t end (** {1 Entries and Trees} *) module Entry : sig type perm = Dir | File | Exec | Link | Submodule type t = { hash : string; name : string; perm : perm } end module Tree : sig type t = { entries : Entry.t list } type tree_node = { entry : Entry.t; children : tree_node list option } val head : repository -> (t, error) Lwt_result.t val expand : repository -> t -> (tree_node list, error) Lwt_result.t val find_path : repository -> string -> ((string * string) list, error) Lwt_result.t end (** {1 Blobs} *) module Blob : sig type t = { content : string } end (** {1 Diffs} *) module Diff : sig include module type of Line_diff val of_commit : repository -> Commit.t -> (file list, error) Lwt_result.t end (** {1 Composite lookups} *) val blob_or_tree : repository -> string -> ([> `Blob of Blob.t | `Tree of Tree.t ], error) Lwt_result.t val object_at_path : repository -> string -> ( [ `Blob of Blob.t | `Tree of Tree.t ] * (string * string) list, error ) Lwt_result.t (** Resolve a slash-separated file path against the HEAD tree. Returns the object and the trail of [(name, hash)] pairs from the root to it. Walks one tree per path segment, so it is cheaper than searching for a hash with {!Tree.find_path}. An empty path is a [Bad_request]; a path that names nothing is [Not_found]. *) (** {1 Repository helpers} *) module Readme : sig type t = { name : string; content : string } end module Repo : sig val readme : repository -> (Readme.t option, error) Lwt_result.t end (** {1 Root helpers} *) val read_root_readme : Config.t -> Blob.t option val scan_subdirectory : Config.t -> string -> (fs_node list, error) result val read_subdir_readme : Config.t -> string -> Blob.t option (** {1 For tests} These helpers have no callers in the server. They are exported so the test suite can exercise repository discovery directly. *) val is_repository : string -> bool val repository_layout_or_none : string -> repository_layout option (** Locate a repository's worktree and git directory. Collapses both "not a repository" and "could not be inspected" to [None]. *) val read_description_file : string -> string val fallback_branch_candidates : Config.t -> string list