@@ -200,6 +200,37 @@ impl Provider for GitHubProvider {
200200 } )
201201 }
202202
203+ async fn update_description (
204+ & self ,
205+ organization : & str ,
206+ repository : & str ,
207+ description : & str ,
208+ ) -> Result < ( ) > {
209+ let endpoint = self
210+ . api_url
211+ . join ( & format ! ( "/repos/{organization}/{repository}" ) )
212+ . with_context ( || {
213+ format ! ( "failed to build GitHub URL for '{organization}/{repository}'" )
214+ } ) ?;
215+
216+ self . client
217+ . patch ( endpoint)
218+ . json ( & UpdateGitHubRepositoryRequest {
219+ description : description. to_string ( ) ,
220+ } )
221+ . send ( )
222+ . await
223+ . with_context ( || {
224+ format ! ( "failed to update description for '{organization}/{repository}'" )
225+ } ) ?
226+ . error_for_status ( )
227+ . with_context ( || {
228+ format ! ( "GitHub update repo API returned an error for '{repository}'" )
229+ } ) ?;
230+
231+ Ok ( ( ) )
232+ }
233+
203234 async fn align_topics (
204235 & self ,
205236 organization : & str ,
@@ -229,6 +260,46 @@ impl Provider for GitHubProvider {
229260 Ok ( ( ) )
230261 }
231262
263+ async fn readme_first_line (
264+ & self ,
265+ organization : & str ,
266+ repository : & str ,
267+ ) -> Result < Option < String > > {
268+ let endpoint = self
269+ . api_url
270+ . join ( & format ! ( "/repos/{organization}/{repository}/readme" ) )
271+ . with_context ( || {
272+ format ! ( "failed to build GitHub README URL for '{organization}/{repository}'" )
273+ } ) ?;
274+
275+ let response = self . client . get ( endpoint) . send ( ) . await . with_context ( || {
276+ format ! ( "failed to fetch README for '{organization}/{repository}'" )
277+ } ) ?;
278+
279+ if response. status ( ) == StatusCode :: NOT_FOUND {
280+ return Ok ( None ) ;
281+ }
282+
283+ let response = response. error_for_status ( ) . with_context ( || {
284+ format ! ( "GitHub README API returned an error for '{repository}'" )
285+ } ) ?;
286+
287+ let readme: GitHubReadmeContent = response. json ( ) . await . with_context ( || {
288+ format ! ( "failed to decode README response for '{repository}'" )
289+ } ) ?;
290+
291+ if readme. content . trim ( ) . is_empty ( ) {
292+ return Ok ( None ) ;
293+ }
294+
295+ let cleaned = readme. content . replace ( '\n' , "" ) ;
296+ let bytes = base64:: engine:: general_purpose:: STANDARD
297+ . decode ( cleaned)
298+ . with_context ( || format ! ( "failed to decode README content for '{repository}'" ) ) ?;
299+ let text = String :: from_utf8_lossy ( & bytes) ;
300+ Ok ( first_readme_line ( & text) )
301+ }
302+
232303 async fn push_file (
233304 & self ,
234305 organization : & str ,
@@ -451,3 +522,37 @@ struct PushGitHubFileRequest {
451522 #[ serde( skip_serializing_if = "Option::is_none" ) ]
452523 sha : Option < String > ,
453524}
525+
526+ #[ derive( Debug , Serialize ) ]
527+ struct UpdateGitHubRepositoryRequest {
528+ description : String ,
529+ }
530+
531+ #[ derive( Debug , Deserialize ) ]
532+ struct GitHubReadmeContent {
533+ #[ serde( default ) ]
534+ content : String ,
535+ }
536+
537+ fn first_readme_line ( markdown : & str ) -> Option < String > {
538+ for line in markdown. lines ( ) {
539+ let mut s = line. trim ( ) ;
540+ if s. is_empty ( ) {
541+ continue ;
542+ }
543+ loop {
544+ let trimmed = s. trim_start_matches ( |c : char | {
545+ c == '#' || c == '>' || c == '-' || c == '*' || c == ' '
546+ } ) ;
547+ if trimmed == s {
548+ break ;
549+ }
550+ s = trimmed;
551+ }
552+ let cleaned = s. trim ( ) . trim_matches ( '`' ) ;
553+ if !cleaned. is_empty ( ) {
554+ return Some ( cleaned. to_string ( ) ) ;
555+ }
556+ }
557+ None
558+ }
0 commit comments