git2/
indexer.rs

1use std::ffi::CStr;
2use std::path::Path;
3use std::{io, marker, mem, ptr};
4
5use libc::c_void;
6
7use crate::odb::{write_pack_progress_cb, OdbPackwriterCb};
8use crate::util::Binding;
9use crate::{raw, Error, IntoCString, Odb};
10
11/// Struct representing the progress by an in-flight transfer.
12pub struct Progress<'a> {
13    pub(crate) raw: ProgressState,
14    pub(crate) _marker: marker::PhantomData<&'a raw::git_indexer_progress>,
15}
16
17pub(crate) enum ProgressState {
18    Borrowed(*const raw::git_indexer_progress),
19    Owned(raw::git_indexer_progress),
20}
21
22/// Callback to be invoked while indexing is in progress.
23///
24/// This callback will be periodically called with updates to the progress of
25/// the indexing so far. The return value indicates whether the indexing or
26/// transfer should continue. A return value of `false` will cancel the
27/// indexing or transfer.
28///
29/// * `progress` - the progress being made so far.
30pub type IndexerProgress<'a> = dyn FnMut(Progress<'_>) -> bool + 'a;
31
32impl<'a> Progress<'a> {
33    /// Number of objects in the packfile being downloaded
34    pub fn total_objects(&self) -> usize {
35        unsafe { (*self.raw()).total_objects as usize }
36    }
37    /// Received objects that have been hashed
38    pub fn indexed_objects(&self) -> usize {
39        unsafe { (*self.raw()).indexed_objects as usize }
40    }
41    /// Objects which have been downloaded
42    pub fn received_objects(&self) -> usize {
43        unsafe { (*self.raw()).received_objects as usize }
44    }
45    /// Locally-available objects that have been injected in order to fix a thin
46    /// pack.
47    pub fn local_objects(&self) -> usize {
48        unsafe { (*self.raw()).local_objects as usize }
49    }
50    /// Number of deltas in the packfile being downloaded
51    pub fn total_deltas(&self) -> usize {
52        unsafe { (*self.raw()).total_deltas as usize }
53    }
54    /// Received deltas that have been hashed.
55    pub fn indexed_deltas(&self) -> usize {
56        unsafe { (*self.raw()).indexed_deltas as usize }
57    }
58    /// Size of the packfile received up to now
59    pub fn received_bytes(&self) -> usize {
60        unsafe { (*self.raw()).received_bytes as usize }
61    }
62
63    /// Convert this to an owned version of `Progress`.
64    pub fn to_owned(&self) -> Progress<'static> {
65        Progress {
66            raw: ProgressState::Owned(unsafe { *self.raw() }),
67            _marker: marker::PhantomData,
68        }
69    }
70}
71
72impl<'a> Binding for Progress<'a> {
73    type Raw = *const raw::git_indexer_progress;
74    unsafe fn from_raw(raw: *const raw::git_indexer_progress) -> Progress<'a> {
75        Progress {
76            raw: ProgressState::Borrowed(raw),
77            _marker: marker::PhantomData,
78        }
79    }
80
81    fn raw(&self) -> *const raw::git_indexer_progress {
82        match self.raw {
83            ProgressState::Borrowed(raw) => raw,
84            ProgressState::Owned(ref raw) => raw as *const _,
85        }
86    }
87}
88
89/// Callback to be invoked while a transfer is in progress.
90///
91/// This callback will be periodically called with updates to the progress of
92/// the transfer so far. The return value indicates whether the transfer should
93/// continue. A return value of `false` will cancel the transfer.
94///
95/// * `progress` - the progress being made so far.
96#[deprecated(
97    since = "0.11.0",
98    note = "renamed to `IndexerProgress` to match upstream"
99)]
100#[allow(dead_code)]
101pub type TransportProgress<'a> = IndexerProgress<'a>;
102
103/// A stream to write and index a packfile
104///
105/// This is equivalent to [`crate::OdbPackwriter`], but allows to store the pack
106/// and index at an arbitrary path. It also does not require access to an object
107/// database if, and only if, the pack file is self-contained (i.e. not "thin").
108pub struct Indexer<'odb> {
109    raw: *mut raw::git_indexer,
110    progress: raw::git_indexer_progress,
111    progress_payload_ptr: *mut OdbPackwriterCb<'odb>,
112}
113
114impl<'a> Indexer<'a> {
115    /// Create a new indexer
116    ///
117    /// The [`Odb`] is used to resolve base objects when fixing thin packs. It
118    /// can be `None` if no thin pack is expected, in which case missing bases
119    /// will result in an error.
120    ///
121    /// `path` is the directory where the packfile should be stored.
122    ///
123    /// `mode` is the permissions to use for the output files, use `0` for defaults.
124    ///
125    /// If `verify` is `false`, the indexer will bypass object connectivity checks.
126    pub fn new(odb: Option<&Odb<'a>>, path: &Path, mode: u32, verify: bool) -> Result<Self, Error> {
127        crate::init();
128        let path = path.into_c_string()?;
129
130        let odb = odb.map(Binding::raw).unwrap_or_else(ptr::null_mut);
131
132        let mut out = ptr::null_mut();
133        let progress_cb: raw::git_indexer_progress_cb = Some(write_pack_progress_cb);
134        let progress_payload = Box::new(OdbPackwriterCb { cb: None });
135        let progress_payload_ptr = Box::into_raw(progress_payload);
136
137        unsafe {
138            let mut opts = mem::zeroed();
139            try_call!(raw::git_indexer_options_init(
140                &mut opts,
141                raw::GIT_INDEXER_OPTIONS_VERSION
142            ));
143            opts.progress_cb = progress_cb;
144            opts.progress_cb_payload = progress_payload_ptr as *mut c_void;
145            opts.verify = verify.into();
146
147            try_call!(raw::git_indexer_new(&mut out, path, mode, odb, &mut opts));
148        }
149
150        Ok(Self {
151            raw: out,
152            progress: Default::default(),
153            progress_payload_ptr,
154        })
155    }
156
157    /// Finalize the pack and index
158    ///
159    /// Resolves any pending deltas and writes out the index file. The returned
160    /// string is the hexadecimal checksum of the packfile, which is also used
161    /// to name the pack and index files (`pack-<checksum>.pack` and
162    /// `pack-<checksum>.idx` respectively).
163    pub fn commit(mut self) -> Result<String, Error> {
164        unsafe {
165            try_call!(raw::git_indexer_commit(self.raw, &mut self.progress));
166
167            let name = CStr::from_ptr(raw::git_indexer_name(self.raw));
168            Ok(name.to_str().expect("pack name not utf8").to_owned())
169        }
170    }
171
172    /// The callback through which progress is monitored. Be aware that this is
173    /// called inline, so performance may be affected.
174    pub fn progress<F>(&mut self, cb: F) -> &mut Self
175    where
176        F: FnMut(Progress<'_>) -> bool + 'a,
177    {
178        let progress_payload =
179            unsafe { &mut *(self.progress_payload_ptr as *mut OdbPackwriterCb<'_>) };
180        progress_payload.cb = Some(Box::new(cb) as Box<IndexerProgress<'a>>);
181
182        self
183    }
184}
185
186impl io::Write for Indexer<'_> {
187    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
188        unsafe {
189            let ptr = buf.as_ptr() as *mut c_void;
190            let len = buf.len();
191
192            let res = raw::git_indexer_append(self.raw, ptr, len, &mut self.progress);
193            if res < 0 {
194                Err(io::Error::new(io::ErrorKind::Other, Error::last_error(res)))
195            } else {
196                Ok(buf.len())
197            }
198        }
199    }
200
201    fn flush(&mut self) -> io::Result<()> {
202        Ok(())
203    }
204}
205
206impl Drop for Indexer<'_> {
207    fn drop(&mut self) {
208        unsafe {
209            raw::git_indexer_free(self.raw);
210            drop(Box::from_raw(self.progress_payload_ptr))
211        }
212    }
213}
214
215#[cfg(test)]
216mod tests {
217    use crate::{Buf, Indexer};
218    use std::io::prelude::*;
219
220    #[test]
221    fn indexer() {
222        let (_td, repo_source) = crate::test::repo_init();
223        let (_td, repo_target) = crate::test::repo_init();
224
225        let mut progress_called = false;
226
227        // Create an in-memory packfile
228        let mut builder = t!(repo_source.packbuilder());
229        let mut buf = Buf::new();
230        let (commit_source_id, _tree) = crate::test::commit(&repo_source);
231        t!(builder.insert_object(commit_source_id, None));
232        t!(builder.write_buf(&mut buf));
233
234        // Write it to the standard location in the target repo, but via indexer
235        let odb = repo_source.odb().unwrap();
236        let mut indexer = Indexer::new(
237            Some(&odb),
238            repo_target.path().join("objects").join("pack").as_path(),
239            0o644,
240            true,
241        )
242        .unwrap();
243        indexer.progress(|_| {
244            progress_called = true;
245            true
246        });
247        indexer.write(&buf).unwrap();
248        indexer.commit().unwrap();
249
250        // Assert that target repo picks it up as valid
251        let commit_target = repo_target.find_commit(commit_source_id).unwrap();
252        assert_eq!(commit_target.id(), commit_source_id);
253        assert!(progress_called);
254    }
255}