@@ -2,7 +2,7 @@ use crate::{actors, web, web::api};
22
33use axum:: extract:: { Path , Query , State } ;
44use axum:: { Json , routing} ;
5- use git2_ox:: commit;
5+ use git2_ox:: { ReferenceKind , ReferenceKindFilter , ResolvedReference , commit} ;
66use serde:: { Deserialize , Serialize } ;
77use utoipa:: { IntoParams , ToSchema } ;
88
@@ -17,10 +17,19 @@ pub fn router() -> routing::Router<web::AppState> {
1717 . route ( "/tags" , routing:: get ( list_tags) . post ( create_tag) )
1818 . route ( "/branches" , routing:: get ( list_branches) . post ( create_branch) )
1919 . route ( "/repository/status" , routing:: get ( get_repository_status) )
20+ . route ( "/references" , routing:: get ( list_references) )
2021}
2122
2223#[ derive( utoipa:: OpenApi ) ]
23- #[ openapi( paths( get_revision, checkout_revision, list_commits, list_tags, create_tag, list_branches, create_branch, get_repository_status, get_diff) , tags( ( name = "Git Repository" , description="Git Repository related endpoints" ) ) ) ]
24+ #[ openapi(
25+ paths(
26+ get_revision, checkout_revision, list_commits, list_tags, create_tag, list_branches, create_branch,
27+ get_repository_status, get_diff, list_references
28+ ) ,
29+ tags(
30+ ( name = "Git Repository" , description="Git Repository related endpoints" )
31+ )
32+ ) ]
2433pub ( super ) struct ApiDoc ;
2534
2635#[ utoipa:: path(
@@ -44,7 +53,7 @@ pub(super) struct ApiDoc;
4453async fn get_revision (
4554 State ( state) : State < web:: AppState > ,
4655 Path ( commit_id) : Path < String > ,
47- ) -> Result < Json < commit:: Commit > , api:: AppError > {
56+ ) -> Result < Json < commit:: CommitWithReferences > , api:: AppError > {
4857 let actor = state. git_actor ( ) ;
4958 let msg = actors:: git:: GetRevision {
5059 revision : commit_id,
@@ -76,7 +85,7 @@ async fn get_revision(
7685async fn checkout_revision (
7786 State ( state) : State < web:: AppState > ,
7887 Path ( commit_id) : Path < String > ,
79- ) -> Result < Json < commit:: Commit > , api:: AppError > {
88+ ) -> Result < Json < commit:: CommitWithReferences > , api:: AppError > {
8089 let actor = state. git_actor ( ) ;
8190 let msg = actors:: git:: CheckoutRevision {
8291 revision : commit_id,
@@ -89,14 +98,17 @@ async fn checkout_revision(
8998#[ serde( rename_all = "camelCase" ) ]
9099struct ListCommitsQuery {
91100 /// string filter for the commits. Filters commits by their ID or summary.
101+ #[ param( nullable = false ) ]
92102 filter : Option < String > ,
93103
94104 // serde(flatten) does not work here, see https://github.com/juhaku/utoipa/issues/841
95105 /// The base revision of the range, this can be short hash, full hash, a tag,
96106 /// or any other reference such a branch name. If empty, the first commit is used.
107+ #[ param( nullable = false ) ]
97108 base_rev : Option < String > ,
98109 /// The head revision of the range, this can be short hash, full hash, a tag,
99110 /// or any other reference such a branch name. If empty, the current HEAD is used.
111+ #[ param( nullable = false ) ]
100112 head_rev : Option < String > ,
101113}
102114
@@ -105,7 +117,7 @@ struct ListCommitsQuery {
105117struct ListCommitsResponse {
106118 /// Array of commits between the base and head commit IDs
107119 /// in reverse chronological order.
108- commits : Vec < git2_ox:: Commit > ,
120+ commits : Vec < git2_ox:: CommitWithReferences > ,
109121}
110122
111123#[ utoipa:: path(
@@ -139,9 +151,11 @@ async fn list_commits(
139151struct CommitRangeQuery {
140152 /// The base revision of the range, this can be short hash, full hash, a tag,
141153 /// or any other reference such a branch name. If empty, the first commit is used.
154+ #[ param( nullable = false ) ]
142155 base_rev : Option < String > ,
143156 /// The head revision of the range, this can be short hash, full hash, a tag,
144157 /// or any other reference such a branch name. If empty, the current HEAD is used.
158+ #[ param( nullable = false ) ]
145159 head_rev : Option < String > ,
146160}
147161
@@ -183,6 +197,7 @@ async fn get_diff(
183197#[ serde( rename_all = "camelCase" ) ]
184198struct ListTagsQuery {
185199 /// String filter against which the tag name is matched.
200+ #[ param( nullable = false ) ]
186201 filter : Option < String > ,
187202}
188203
@@ -260,6 +275,7 @@ struct ListBranchesResponse {
260275#[ serde( rename_all = "camelCase" ) ]
261276struct ListBranchesQuery {
262277 /// string filter against with the branch name is matched
278+ #[ param( nullable = false ) ]
263279 filter : Option < String > ,
264280}
265281
@@ -326,7 +342,7 @@ async fn create_branch(
326342#[ serde( rename_all = "camelCase" ) ]
327343struct RepositoryStatusResponse {
328344 /// The current HEAD commit
329- head : git2_ox:: Commit ,
345+ head : git2_ox:: CommitWithReferences ,
330346 /// The current branch name, not set if in a detached HEAD state
331347 current_branch : Option < String > ,
332348}
@@ -352,3 +368,61 @@ async fn get_repository_status(
352368 current_branch : status. current_branch ,
353369 } ) )
354370}
371+
372+ #[ derive( Deserialize , IntoParams ) ]
373+ #[ serde( rename_all = "camelCase" ) ]
374+ struct ListReferencesQuery {
375+ /// String filter against with the reference name
376+ #[ param( nullable = false ) ]
377+ filter : Option < String > ,
378+ // Ideally, we would use ReferenceKindFilter with serde(flatten) here,
379+ // but IntoParams does not does not respect it,
380+ // see https://github.com/juhaku/utoipa/issues/841
381+ /// Reference kinds to include, mutually exclusive with `exclude`
382+ #[ param( min_items = 1 , nullable = false ) ]
383+ include : Option < Vec < ReferenceKind > > ,
384+ /// Reference kinds to exclude, mutually exclusive with `include`
385+ #[ param( min_items = 1 , nullable = false ) ]
386+ exclude : Option < Vec < ReferenceKind > > ,
387+ }
388+
389+ #[ derive( ToSchema , Serialize , IntoParams ) ]
390+ #[ serde( rename_all = "camelCase" ) ]
391+ struct ListReferencesResponse {
392+ /// Array of references
393+ references : Vec < ResolvedReference > ,
394+ }
395+
396+ /// List references in the repository
397+ #[ utoipa:: path(
398+ get,
399+ path = "/references" ,
400+ summary = "List references" ,
401+ description = "List all references in the repository, optionally filtered by a glob pattern and type." ,
402+ params( ListReferencesQuery ) ,
403+ responses(
404+ ( status = http:: StatusCode :: OK , description = "List of references" , body = ListReferencesResponse ) ,
405+ ( status = http:: StatusCode :: INTERNAL_SERVER_ERROR , description = "Internal server error" , body = api:: ApiStatusDetailResponse ) ,
406+ )
407+ ) ]
408+ async fn list_references (
409+ State ( state) : State < web:: AppState > ,
410+ axum_extra:: extract:: Query ( query) : axum_extra:: extract:: Query < ListReferencesQuery > ,
411+ ) -> Result < Json < ListReferencesResponse > , api:: AppError > {
412+ let filter_kinds = match ( query. include , query. exclude ) {
413+ ( Some ( _) , Some ( _) ) => Err ( api:: AppError :: BadRequest (
414+ "Include and exclude filters are mutually exclusive" . to_string ( ) ,
415+ ) ) ,
416+ ( Some ( include) , None ) => Ok ( Some ( ReferenceKindFilter :: include ( include) ) ) ,
417+ ( None , Some ( exclude) ) => Ok ( Some ( ReferenceKindFilter :: exclude ( exclude) ) ) ,
418+ ( None , None ) => Ok ( None ) ,
419+ } ?;
420+
421+ let actor = state. git_actor ( ) ;
422+ let msg = actors:: git:: ListReferences {
423+ filter : query. filter ,
424+ filter_kinds,
425+ } ;
426+ let references = actor. call ( msg) . await ??;
427+ Ok ( Json ( ListReferencesResponse { references } ) )
428+ }
0 commit comments