@@ -7,7 +7,7 @@ use std::path::Path;
7
7
use std:: ptr;
8
8
9
9
use crate :: util:: { self , Binding } ;
10
- use crate :: { panic, raw, Error , FetchOptions , IntoCString , Repository } ;
10
+ use crate :: { panic, raw, Error , FetchOptions , IntoCString , Oid , Repository , Tree } ;
11
11
use crate :: { CheckoutNotificationType , DiffFile , Remote } ;
12
12
13
13
/// A builder struct which is used to build configuration for cloning a new git
@@ -64,6 +64,12 @@ pub struct RepoBuilder<'cb> {
64
64
pub type RemoteCreate < ' cb > =
65
65
dyn for < ' a > FnMut ( & ' a Repository , & str , & str ) -> Result < Remote < ' a > , Error > + ' cb ;
66
66
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
+
67
73
/// A builder struct for configuring checkouts of a repository.
68
74
pub struct CheckoutBuilder < ' cb > {
69
75
their_label : Option < CString > ,
@@ -674,9 +680,82 @@ extern "C" fn notify_cb(
674
680
. unwrap_or ( 2 )
675
681
}
676
682
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
+ ///
720
+ /// The mode given must be one of 0o040000, 0o100644, 0o100755, 0o120000 or
721
+ /// 0o160000 currently.
722
+ pub fn upsert < T : IntoCString > ( & mut self , path : T , id : Oid , filemode : i32 ) -> & mut Self {
723
+ let filemode = filemode as raw:: git_filemode_t ;
724
+ let path = util:: cstring_to_repo_path ( path) . unwrap ( ) ;
725
+ let path_ptr = path. as_ptr ( ) ;
726
+ self . paths . push ( path) ;
727
+ self . updates . push ( raw:: git_tree_update {
728
+ action : raw:: GIT_TREE_UPDATE_UPSERT ,
729
+ id : unsafe { * id. raw ( ) } ,
730
+ filemode,
731
+ path : path_ptr,
732
+ } ) ;
733
+ self
734
+ }
735
+
736
+ /// Create a new tree from the specified baseline and this series of updates.
737
+ ///
738
+ /// The baseline tree must exist in the specified repository.
739
+ pub fn create_updated ( & mut self , repo : & Repository , baseline : & Tree < ' _ > ) -> Result < Oid , Error > {
740
+ let mut ret = raw:: git_oid {
741
+ id : [ 0 ; raw:: GIT_OID_RAWSZ ] ,
742
+ } ;
743
+ unsafe {
744
+ try_call ! ( raw:: git_tree_create_updated(
745
+ & mut ret,
746
+ repo. raw( ) ,
747
+ baseline. raw( ) ,
748
+ self . updates. len( ) ,
749
+ self . updates. as_ptr( )
750
+ ) ) ;
751
+ Ok ( Binding :: from_raw ( & ret as * const _ ) )
752
+ }
753
+ }
754
+ }
755
+
677
756
#[ cfg( test) ]
678
757
mod tests {
679
- use super :: { CheckoutBuilder , RepoBuilder } ;
758
+ use super :: { CheckoutBuilder , RepoBuilder , TreeUpdateBuilder } ;
680
759
use crate :: { CheckoutNotificationType , Repository } ;
681
760
use std:: fs;
682
761
use std:: path:: Path ;
@@ -707,6 +786,23 @@ mod tests {
707
786
assert ! ( RepoBuilder :: new( ) . branch( "foo" ) . clone( & url, & dst) . is_err( ) ) ;
708
787
}
709
788
789
+ #[ test]
790
+ fn smoke_tree_create_updated ( ) {
791
+ let ( _tempdir, repo) = crate :: test:: repo_init ( ) ;
792
+ let ( _, tree_id) = crate :: test:: commit ( & repo) ;
793
+ let tree = t ! ( repo. find_tree( tree_id) ) ;
794
+ assert ! ( tree. get_name( "bar" ) . is_none( ) ) ;
795
+ let foo_id = tree. get_name ( "foo" ) . unwrap ( ) . id ( ) ;
796
+ let tree2_id = t ! ( TreeUpdateBuilder :: new( )
797
+ . remove( "foo" )
798
+ . upsert( "bar/baz" , foo_id, 0o100644 )
799
+ . create_updated( & repo, & tree) ) ;
800
+ let tree2 = t ! ( repo. find_tree( tree2_id) ) ;
801
+ assert ! ( tree2. get_name( "foo" ) . is_none( ) ) ;
802
+ let baz_id = tree2. get_path ( Path :: new ( "bar/baz" ) ) . unwrap ( ) . id ( ) ;
803
+ assert_eq ! ( foo_id, baz_id) ;
804
+ }
805
+
710
806
/// Issue regression test #365
711
807
#[ test]
712
808
fn notify_callback ( ) {
0 commit comments