@@ -7,8 +7,8 @@ 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 } ;
11
- use crate :: { CheckoutNotificationType , DiffFile , Remote } ;
10
+ use crate :: { panic, raw, Error , FetchOptions , IntoCString , Oid , Repository , Tree } ;
11
+ use crate :: { CheckoutNotificationType , DiffFile , FileMode , Remote } ;
12
12
13
13
/// A builder struct which is used to build configuration for cloning a new git
14
14
/// repository.
@@ -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,10 +680,79 @@ 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
+ 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
+
677
752
#[ cfg( test) ]
678
753
mod tests {
679
- use super :: { CheckoutBuilder , RepoBuilder } ;
680
- use crate :: { CheckoutNotificationType , Repository } ;
754
+ use super :: { CheckoutBuilder , RepoBuilder , TreeUpdateBuilder } ;
755
+ use crate :: { CheckoutNotificationType , FileMode , Repository } ;
681
756
use std:: fs;
682
757
use std:: path:: Path ;
683
758
use tempfile:: TempDir ;
@@ -707,6 +782,23 @@ mod tests {
707
782
assert ! ( RepoBuilder :: new( ) . branch( "foo" ) . clone( & url, & dst) . is_err( ) ) ;
708
783
}
709
784
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
+
710
802
/// Issue regression test #365
711
803
#[ test]
712
804
fn notify_callback ( ) {
0 commit comments