@@ -7,8 +7,8 @@ use std::path::Path;
77use std:: ptr;
88
99use crate :: util:: { self , Binding } ;
10- use crate :: { panic, raw, Error , FetchOptions , IntoCString , Repository } ;
11- use crate :: { CheckoutNotificationType , DiffFile , Remote } ;
10+ use crate :: { panic, raw, Error , FetchOptions , IntoCString , Oid , Repository , Tree } ;
11+ use crate :: { CheckoutNotificationType , DiffFile , FileMode , Remote } ;
1212
1313/// A builder struct which is used to build configuration for cloning a new git
1414/// repository.
@@ -64,6 +64,12 @@ pub struct RepoBuilder<'cb> {
6464pub type RemoteCreate < ' cb > =
6565 dyn for < ' a > FnMut ( & ' a Repository , & str , & str ) -> Result < Remote < ' a > , Error > + ' cb ;
6666
67+ /// A builder struct for git tree updates, for use with `git_tree_create_updated`.
68+ pub struct TreeUpdateBuilder {
69+ updates : Vec < raw:: git_tree_update > ,
70+ paths : Vec < CString > ,
71+ }
72+
6773/// A builder struct for configuring checkouts of a repository.
6874pub struct CheckoutBuilder < ' cb > {
6975 their_label : Option < CString > ,
@@ -674,10 +680,79 @@ extern "C" fn notify_cb(
674680 . unwrap_or ( 2 )
675681}
676682
683+ impl Default for TreeUpdateBuilder {
684+ fn default ( ) -> Self {
685+ Self :: new ( )
686+ }
687+ }
688+
689+ impl TreeUpdateBuilder {
690+ /// Create a new empty series of updates.
691+ pub fn new ( ) -> Self {
692+ Self {
693+ updates : Vec :: new ( ) ,
694+ paths : Vec :: new ( ) ,
695+ }
696+ }
697+
698+ /// Add an update removing the specified `path` from a tree.
699+ pub fn remove < T : IntoCString > ( & mut self , path : T ) -> & mut Self {
700+ let path = util:: cstring_to_repo_path ( path) . unwrap ( ) ;
701+ let path_ptr = path. as_ptr ( ) ;
702+ self . paths . push ( path) ;
703+ self . updates . push ( raw:: git_tree_update {
704+ action : raw:: GIT_TREE_UPDATE_REMOVE ,
705+ id : raw:: git_oid {
706+ id : [ 0 ; raw:: GIT_OID_RAWSZ ] ,
707+ } ,
708+ filemode : raw:: GIT_FILEMODE_UNREADABLE ,
709+ path : path_ptr,
710+ } ) ;
711+ self
712+ }
713+
714+ /// Add an update setting the specified `path` to a specific Oid, whether it currently exists
715+ /// or not.
716+ ///
717+ /// Note that libgit2 does not support an upsert of a previously removed path, or an upsert
718+ /// that changes the type of an object (such as from tree to blob or vice versa).
719+ pub fn upsert < T : IntoCString > ( & mut self , path : T , id : Oid , filemode : FileMode ) -> & mut Self {
720+ let path = util:: cstring_to_repo_path ( path) . unwrap ( ) ;
721+ let path_ptr = path. as_ptr ( ) ;
722+ self . paths . push ( path) ;
723+ self . updates . push ( raw:: git_tree_update {
724+ action : raw:: GIT_TREE_UPDATE_UPSERT ,
725+ id : unsafe { * id. raw ( ) } ,
726+ filemode : u32:: from ( filemode) as raw:: git_filemode_t ,
727+ path : path_ptr,
728+ } ) ;
729+ self
730+ }
731+
732+ /// Create a new tree from the specified baseline and this series of updates.
733+ ///
734+ /// The baseline tree must exist in the specified repository.
735+ pub fn create_updated ( & mut self , repo : & Repository , baseline : & Tree < ' _ > ) -> Result < Oid , Error > {
736+ let mut ret = raw:: git_oid {
737+ id : [ 0 ; raw:: GIT_OID_RAWSZ ] ,
738+ } ;
739+ unsafe {
740+ try_call ! ( raw:: git_tree_create_updated(
741+ & mut ret,
742+ repo. raw( ) ,
743+ baseline. raw( ) ,
744+ self . updates. len( ) ,
745+ self . updates. as_ptr( )
746+ ) ) ;
747+ Ok ( Binding :: from_raw ( & ret as * const _ ) )
748+ }
749+ }
750+ }
751+
677752#[ cfg( test) ]
678753mod tests {
679- use super :: { CheckoutBuilder , RepoBuilder } ;
680- use crate :: { CheckoutNotificationType , Repository } ;
754+ use super :: { CheckoutBuilder , RepoBuilder , TreeUpdateBuilder } ;
755+ use crate :: { CheckoutNotificationType , FileMode , Repository } ;
681756 use std:: fs;
682757 use std:: path:: Path ;
683758 use tempfile:: TempDir ;
@@ -707,6 +782,23 @@ mod tests {
707782 assert ! ( RepoBuilder :: new( ) . branch( "foo" ) . clone( & url, & dst) . is_err( ) ) ;
708783 }
709784
785+ #[ test]
786+ fn smoke_tree_create_updated ( ) {
787+ let ( _tempdir, repo) = crate :: test:: repo_init ( ) ;
788+ let ( _, tree_id) = crate :: test:: commit ( & repo) ;
789+ let tree = t ! ( repo. find_tree( tree_id) ) ;
790+ assert ! ( tree. get_name( "bar" ) . is_none( ) ) ;
791+ let foo_id = tree. get_name ( "foo" ) . unwrap ( ) . id ( ) ;
792+ let tree2_id = t ! ( TreeUpdateBuilder :: new( )
793+ . remove( "foo" )
794+ . upsert( "bar/baz" , foo_id, FileMode :: Blob )
795+ . create_updated( & repo, & tree) ) ;
796+ let tree2 = t ! ( repo. find_tree( tree2_id) ) ;
797+ assert ! ( tree2. get_name( "foo" ) . is_none( ) ) ;
798+ let baz_id = tree2. get_path ( Path :: new ( "bar/baz" ) ) . unwrap ( ) . id ( ) ;
799+ assert_eq ! ( foo_id, baz_id) ;
800+ }
801+
710802 /// Issue regression test #365
711803 #[ test]
712804 fn notify_callback ( ) {
0 commit comments