1
+ use std:: ffi:: CStr ;
1
2
use std:: io;
2
3
use std:: marker;
4
+ use std:: mem;
3
5
use std:: mem:: MaybeUninit ;
6
+ use std:: path:: Path ;
4
7
use std:: ptr;
5
8
use std:: slice;
6
9
@@ -10,6 +13,7 @@ use libc::{c_char, c_int, c_uint, c_void, size_t};
10
13
11
14
use crate :: panic;
12
15
use crate :: util:: Binding ;
16
+ use crate :: IntoCString ;
13
17
use crate :: {
14
18
raw, Error , IndexerProgress , Mempack , Object , ObjectType , OdbLookupFlags , Oid , Progress ,
15
19
} ;
@@ -183,6 +187,41 @@ impl<'repo> Odb<'repo> {
183
187
} )
184
188
}
185
189
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
+
186
225
/// Checks if the object database has an object.
187
226
pub fn exists ( & self , oid : Oid ) -> bool {
188
227
unsafe { raw:: git_odb_exists ( self . raw , oid. raw ( ) ) != 0 }
@@ -519,6 +558,78 @@ impl<'repo> Drop for OdbPackwriter<'repo> {
519
558
}
520
559
}
521
560
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
+
522
633
pub type ForeachCb < ' a > = dyn FnMut ( & Oid ) -> bool + ' a ;
523
634
524
635
struct ForeachCbData < ' a > {
@@ -728,4 +839,39 @@ mod tests {
728
839
t ! ( repo. reset( commit1. as_object( ) , ResetType :: Hard , None ) ) ;
729
840
assert ! ( foo_file. exists( ) ) ;
730
841
}
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
+ }
731
877
}
0 commit comments