@@ -226,6 +226,38 @@ impl RepositoryClient<'_> {
226226 Ok ( ( ) )
227227 }
228228
229+ pub ( crate ) fn update_ref ( & mut self , name : & str , sha : & str , force : bool ) -> anyhow:: Result < ( ) > {
230+ // This mostly exists to make sure the request is successful rather than
231+ // really checking the created ref (which we already know).
232+ #[ derive( serde:: Deserialize ) ]
233+ struct CreatedRef {
234+ #[ serde( rename = "ref" ) ]
235+ #[ allow( unused) ]
236+ ref_ : String ,
237+ }
238+ #[ derive( serde:: Serialize ) ]
239+ struct UpdateRefInternal < ' a > {
240+ sha : & ' a str ,
241+ force : bool ,
242+ }
243+
244+ self . start_new_request ( ) ?;
245+ // We want curl to read the request body, so configure POST.
246+ self . github . client . post ( true ) ?;
247+ // However, the actual request should be a PATCH request.
248+ self . github . client . custom_request ( "PATCH" ) ?;
249+ self . github . client . url ( & format ! (
250+ "https://api.github.com/repos/{repository}/git/refs/{name}" ,
251+ repository = self . repo,
252+ ) ) ?;
253+ self . github
254+ . client
255+ . with_body ( UpdateRefInternal { sha, force } )
256+ . send_with_response :: < CreatedRef > ( ) ?;
257+
258+ Ok ( ( ) )
259+ }
260+
229261 pub ( crate ) fn workflow_dispatch ( & mut self , workflow : & str , branch : & str ) -> anyhow:: Result < ( ) > {
230262 #[ derive( serde:: Serialize ) ]
231263 struct Request < ' a > {
@@ -309,6 +341,71 @@ impl RepositoryClient<'_> {
309341 . send ( ) ?;
310342 Ok ( ( ) )
311343 }
344+
345+ /// Returns the last commit (SHA) on a repository's default branch which changed
346+ /// the passed path.
347+ pub ( crate ) fn last_commit_for_file ( & mut self , path : & str ) -> anyhow:: Result < String > {
348+ #[ derive( serde:: Deserialize ) ]
349+ struct CommitData {
350+ sha : String ,
351+ }
352+ self . start_new_request ( ) ?;
353+ self . github . client . get ( true ) ?;
354+ self . github . client . url ( & format ! (
355+ "https://api.github.com/repos/{repo}/commits?path={path}" ,
356+ repo = self . repo
357+ ) ) ?;
358+ let mut commits = self
359+ . github
360+ . client
361+ . without_body ( )
362+ . send_with_response :: < Vec < CommitData > > ( ) ?;
363+ if commits. is_empty ( ) {
364+ anyhow:: bail!( "No commits for path {:?}" , path) ;
365+ }
366+ Ok ( commits. remove ( 0 ) . sha )
367+ }
368+
369+ /// Returns the contents of the file
370+ pub ( crate ) fn read_file ( & mut self , sha : Option < & str > , path : & str ) -> anyhow:: Result < GitFile > {
371+ self . start_new_request ( ) ?;
372+ self . github . client . get ( true ) ?;
373+ self . github . client . url ( & format ! (
374+ "https://api.github.com/repos/{repo}/contents/{path}{maybe_ref}" ,
375+ repo = self . repo,
376+ maybe_ref = sha. map( |s| format!( "?ref={}" , s) ) . unwrap_or_default( )
377+ ) ) ?;
378+ self . github
379+ . client
380+ . without_body ( )
381+ . send_with_response :: < GitFile > ( )
382+ }
383+ }
384+
385+ #[ derive( Debug , serde:: Deserialize ) ]
386+ #[ serde( tag = "type" , rename_all = "lowercase" ) ]
387+ pub ( crate ) enum GitFile {
388+ File { encoding : String , content : String } ,
389+ Submodule { sha : String } ,
390+ }
391+
392+ impl GitFile {
393+ pub ( crate ) fn submodule_sha ( & self ) -> & str {
394+ if let GitFile :: Submodule { sha } = self {
395+ sha
396+ } else {
397+ panic ! ( "{:?} not a submodule" , self ) ;
398+ }
399+ }
400+
401+ pub ( crate ) fn content ( & self ) -> anyhow:: Result < String > {
402+ if let GitFile :: File { encoding, content } = self {
403+ assert_eq ! ( encoding, "base64" ) ;
404+ Ok ( String :: from_utf8 ( base64:: decode ( & content. trim ( ) ) ?) ?)
405+ } else {
406+ panic ! ( "content() on {:?}" , self ) ;
407+ }
408+ }
312409}
313410
314411#[ derive( Copy , Clone ) ]
0 commit comments