1+ use std:: ffi:: CStr ;
12use std:: io;
23use std:: marker;
4+ use std:: mem;
35use std:: mem:: MaybeUninit ;
6+ use std:: path:: Path ;
47use std:: ptr;
58use std:: slice;
69
@@ -10,6 +13,7 @@ use libc::{c_char, c_int, c_uint, c_void, size_t};
1013
1114use crate :: panic;
1215use crate :: util:: Binding ;
16+ use crate :: IntoCString ;
1317use crate :: {
1418 raw, Error , IndexerProgress , Mempack , Object , ObjectType , OdbLookupFlags , Oid , Progress ,
1519} ;
@@ -183,6 +187,41 @@ impl<'repo> Odb<'repo> {
183187 } )
184188 }
185189
190+ /// Create a stream for writing a pack file to an arbitrary path
191+ ///
192+ /// This [`Odb`] is used to resolve objects if the written pack is "thin", i.e. depends on
193+ /// already-known objects.
194+ ///
195+ /// `mode` are the file permissions to use on the output.
196+ pub fn indexer ( & self , path : & Path , mode : u32 ) -> Result < Indexer < ' _ > , Error > {
197+ let path = path. into_c_string ( ) ?;
198+ let mut out = ptr:: null_mut ( ) ;
199+ let progress = MaybeUninit :: uninit ( ) ;
200+ let progress_cb: raw:: git_indexer_progress_cb = Some ( write_pack_progress_cb) ;
201+ let progress_payload = Box :: new ( OdbPackwriterCb { cb : None } ) ;
202+ let progress_payload_ptr = Box :: into_raw ( progress_payload) ;
203+
204+ unsafe {
205+ let mut opts = mem:: zeroed ( ) ;
206+ try_call ! ( raw:: git_indexer_options_init(
207+ & mut opts,
208+ raw:: GIT_INDEXER_OPTIONS_VERSION
209+ ) ) ;
210+ opts. progress_cb = progress_cb;
211+ opts. progress_cb_payload = progress_payload_ptr as * mut c_void ;
212+
213+ try_call ! ( raw:: git_indexer_new(
214+ & mut out, path, mode, self . raw, & mut opts
215+ ) ) ;
216+ }
217+
218+ Ok ( Indexer {
219+ raw : out,
220+ progress,
221+ progress_payload_ptr,
222+ } )
223+ }
224+
186225 /// Checks if the object database has an object.
187226 pub fn exists ( & self , oid : Oid ) -> bool {
188227 unsafe { raw:: git_odb_exists ( self . raw , oid. raw ( ) ) != 0 }
@@ -519,6 +558,78 @@ impl<'repo> Drop for OdbPackwriter<'repo> {
519558 }
520559}
521560
561+ /// A stream to write and index a packfile
562+ ///
563+ /// This is a lower-level interface than [`OdbPackwriter`] which allows to write the pack data and
564+ /// index to an arbitrary path, but is otherwise equivalent.
565+ pub struct Indexer < ' odb > {
566+ raw : * mut raw:: git_indexer ,
567+ progress : MaybeUninit < raw:: git_indexer_progress > ,
568+ progress_payload_ptr : * mut OdbPackwriterCb < ' odb > ,
569+ }
570+
571+ impl < ' a > Indexer < ' a > {
572+ /// Finalize the pack and index
573+ ///
574+ /// Resolves any pending deltas and writes out the index file. The returned string is the
575+ /// hexadecimal checksum of the packfile, which is also used to name the pack and index files
576+ /// (`pack-<checksum>.pack` and `pack-<checksum>.idx` respectively).
577+ pub fn commit ( mut self ) -> Result < String , Error > {
578+ unsafe {
579+ try_call ! ( raw:: git_indexer_commit(
580+ self . raw,
581+ self . progress. as_mut_ptr( )
582+ ) ) ;
583+
584+ let name = CStr :: from_ptr ( raw:: git_indexer_name ( self . raw ) ) ;
585+ Ok ( name. to_str ( ) . expect ( "pack name not utf8" ) . to_owned ( ) )
586+ }
587+ }
588+
589+ /// The callback through which progress is monitored. Be aware that this is
590+ /// called inline, so performance may be affected.
591+ pub fn progress < F > ( & mut self , cb : F ) -> & mut Self
592+ where
593+ F : FnMut ( Progress < ' _ > ) -> bool + ' a ,
594+ {
595+ let progress_payload =
596+ unsafe { & mut * ( self . progress_payload_ptr as * mut OdbPackwriterCb < ' _ > ) } ;
597+ progress_payload. cb = Some ( Box :: new ( cb) as Box < IndexerProgress < ' a > > ) ;
598+
599+ self
600+ }
601+ }
602+
603+ impl io:: Write for Indexer < ' _ > {
604+ fn write ( & mut self , buf : & [ u8 ] ) -> io:: Result < usize > {
605+ unsafe {
606+ let ptr = buf. as_ptr ( ) as * mut c_void ;
607+ let len = buf. len ( ) ;
608+
609+ let res = raw:: git_indexer_append ( self . raw , ptr, len, self . progress . as_mut_ptr ( ) ) ;
610+
611+ if res < 0 {
612+ Err ( io:: Error :: new ( io:: ErrorKind :: Other , "Write error" ) )
613+ } else {
614+ Ok ( buf. len ( ) )
615+ }
616+ }
617+ }
618+
619+ fn flush ( & mut self ) -> io:: Result < ( ) > {
620+ Ok ( ( ) )
621+ }
622+ }
623+
624+ impl Drop for Indexer < ' _ > {
625+ fn drop ( & mut self ) {
626+ unsafe {
627+ raw:: git_indexer_free ( self . raw ) ;
628+ drop ( Box :: from_raw ( self . progress_payload_ptr ) )
629+ }
630+ }
631+ }
632+
522633pub type ForeachCb < ' a > = dyn FnMut ( & Oid ) -> bool + ' a ;
523634
524635struct ForeachCbData < ' a > {
@@ -728,4 +839,39 @@ mod tests {
728839 t ! ( repo. reset( commit1. as_object( ) , ResetType :: Hard , None ) ) ;
729840 assert ! ( foo_file. exists( ) ) ;
730841 }
842+
843+ #[ test]
844+ fn indexer ( ) {
845+ let ( _td, repo_source) = crate :: test:: repo_init ( ) ;
846+ let ( _td, repo_target) = crate :: test:: repo_init ( ) ;
847+
848+ let mut progress_called = false ;
849+
850+ // Create an in-memory packfile
851+ let mut builder = t ! ( repo_source. packbuilder( ) ) ;
852+ let mut buf = Buf :: new ( ) ;
853+ let ( commit_source_id, _tree) = crate :: test:: commit ( & repo_source) ;
854+ t ! ( builder. insert_object( commit_source_id, None ) ) ;
855+ t ! ( builder. write_buf( & mut buf) ) ;
856+
857+ // Write it to the standard location in the target repo, but via indexer
858+ let odb = repo_source. odb ( ) . unwrap ( ) ;
859+ let mut indexer = odb
860+ . indexer (
861+ repo_target. path ( ) . join ( "objects" ) . join ( "pack" ) . as_path ( ) ,
862+ 0o644 ,
863+ )
864+ . unwrap ( ) ;
865+ indexer. progress ( |_| {
866+ progress_called = true ;
867+ true
868+ } ) ;
869+ indexer. write ( & buf) . unwrap ( ) ;
870+ indexer. commit ( ) . unwrap ( ) ;
871+
872+ // Assert that target repo picks it up as valid
873+ let commit_target = repo_target. find_commit ( commit_source_id) . unwrap ( ) ;
874+ assert_eq ! ( commit_target. id( ) , commit_source_id) ;
875+ assert ! ( progress_called) ;
876+ }
731877}
0 commit comments