From 1cff70eb934dd5499c3b2e6c563c9694de575f19 Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Mon, 1 Jun 2026 16:34:02 -0700 Subject: [PATCH 01/39] [Unsafety minimization] Reduce `unsafe` blocks in src/attr.rs --- src/attr.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/attr.rs b/src/attr.rs index 33b1d2d4af..a9460f5936 100644 --- a/src/attr.rs +++ b/src/attr.rs @@ -22,15 +22,17 @@ pub enum AttrValue<'string> { } macro_rules! from_value { - ($value:expr => $string:expr) => { - match unsafe { raw::git_attr_value($value.map_or(ptr::null(), |v| v.as_ptr().cast())) } { + ($value:expr => $string:expr) => {{ + let ptr = $value.map_or(ptr::null(), |v| v.as_ptr().cast()); + let value = unsafe { raw::git_attr_value(ptr) }; + match value { raw::GIT_ATTR_VALUE_TRUE => Self::True, raw::GIT_ATTR_VALUE_FALSE => Self::False, raw::GIT_ATTR_VALUE_STRING => $string, raw::GIT_ATTR_VALUE_UNSPECIFIED => Self::Unspecified, _ => unreachable!(), } - }; + }}; } impl<'string> AttrValue<'string> { From 564bf421fba891ed662b5168127e31b82aecdbf1 Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Mon, 1 Jun 2026 16:34:16 -0700 Subject: [PATCH 02/39] [Unsafety minimization] Reduce `unsafe` blocks in src/blame.rs --- src/blame.rs | 35 +++++++++++++++-------------------- 1 file changed, 15 insertions(+), 20 deletions(-) diff --git a/src/blame.rs b/src/blame.rs index 2d4b7eaddd..52c0255198 100644 --- a/src/blame.rs +++ b/src/blame.rs @@ -61,26 +61,22 @@ impl<'repo> Blame<'repo> { /// Gets the blame hunk at the given index. pub fn get_index(&self, index: usize) -> Option> { - unsafe { - let ptr = raw::git_blame_get_hunk_byindex(self.raw(), index as u32); - if ptr.is_null() { - None - } else { - Some(BlameHunk::from_raw_const(ptr)) - } + let ptr = unsafe { raw::git_blame_get_hunk_byindex(self.raw(), index as u32) }; + if ptr.is_null() { + None + } else { + Some(unsafe { BlameHunk::from_raw_const(ptr) }) } } /// Gets the hunk that relates to the given line number in the newest /// commit. pub fn get_line(&self, lineno: usize) -> Option> { - unsafe { - let ptr = raw::git_blame_get_hunk_byline(self.raw(), lineno); - if ptr.is_null() { - None - } else { - Some(BlameHunk::from_raw_const(ptr)) - } + let ptr = unsafe { raw::git_blame_get_hunk_byline(self.raw(), lineno) }; + if ptr.is_null() { + None + } else { + Some(unsafe { BlameHunk::from_raw_const(ptr) }) } } @@ -181,12 +177,11 @@ impl<'blame> BlameHunk<'blame> { /// /// Note: `None` could be returned for non-unicode paths on Windows. pub fn path(&self) -> Option<&Path> { - unsafe { - if let Some(bytes) = crate::opt_bytes(self, (*self.raw).orig_path) { - Some(util::bytes2path(bytes)) - } else { - None - } + let opt_bytes = unsafe { crate::opt_bytes(self, (*self.raw).orig_path) }; + if let Some(bytes) = opt_bytes { + Some(util::bytes2path(bytes)) + } else { + None } } From b5ae308b60c7415983577fb2e13a2d73c16d5a33 Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Mon, 1 Jun 2026 16:34:32 -0700 Subject: [PATCH 03/39] [Unsafety minimization] Reduce `unsafe` blocks in src/blob.rs --- src/blob.rs | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/blob.rs b/src/blob.rs index f11c76dd1b..68cc72c294 100644 --- a/src/blob.rs +++ b/src/blob.rs @@ -133,17 +133,16 @@ impl<'repo> Drop for BlobWriter<'repo> { impl<'repo> io::Write for BlobWriter<'repo> { fn write(&mut self, buf: &[u8]) -> io::Result { - unsafe { - if let Some(f) = (*self.raw).write { - let res = f(self.raw, buf.as_ptr() as *const _, buf.len()); - if res < 0 { - Err(io::Error::new(io::ErrorKind::Other, "Write error")) - } else { - Ok(buf.len()) - } + let write_cb = unsafe { (*self.raw).write }; + if let Some(f) = write_cb { + let res = f(self.raw, buf.as_ptr() as *const _, buf.len()); + if res < 0 { + Err(io::Error::new(io::ErrorKind::Other, "Write error")) } else { - Err(io::Error::new(io::ErrorKind::Other, "no write callback")) + Ok(buf.len()) } + } else { + Err(io::Error::new(io::ErrorKind::Other, "no write callback")) } } fn flush(&mut self) -> io::Result<()> { From 7dc802e6676f8e0701891991a690d3108056ae4a Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Mon, 1 Jun 2026 16:34:32 -0700 Subject: [PATCH 04/39] [Unsafety minimization] Reduce `unsafe` blocks in src/branch.rs --- src/branch.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/branch.rs b/src/branch.rs index e1eba99c2b..6ce29c0d1e 100644 --- a/src/branch.rs +++ b/src/branch.rs @@ -119,8 +119,8 @@ impl<'repo> Branch<'repo> { self.get().raw(), upstream_name )); - Ok(()) } + Ok(()) } } From 650a0a70200f2d73196e3b8a33341a183df3406d Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Mon, 1 Jun 2026 16:34:32 -0700 Subject: [PATCH 05/39] [Unsafety minimization] Reduce `unsafe` blocks in src/cert.rs --- src/cert.rs | 89 ++++++++++++++++++++++++++--------------------------- 1 file changed, 43 insertions(+), 46 deletions(-) diff --git a/src/cert.rs b/src/cert.rs index b232cc3ce8..9be394c7f9 100644 --- a/src/cert.rs +++ b/src/cert.rs @@ -92,12 +92,11 @@ impl<'a> Cert<'a> { fn cast(&self, kind: raw::git_cert_t) -> Option<&T> { assert_eq!(mem::size_of::>(), mem::size_of::()); - unsafe { - if kind == (*self.raw).cert_type { - Some(&*(self as *const Cert<'a> as *const T)) - } else { - None - } + let current_kind = unsafe { (*self.raw).cert_type }; + if kind == current_kind { + Some(unsafe { &*(self as *const Cert<'a> as *const T) }) + } else { + None } } } @@ -105,68 +104,66 @@ impl<'a> Cert<'a> { impl<'a> CertHostkey<'a> { /// Returns the md5 hash of the hostkey, if available. pub fn hash_md5(&self) -> Option<&[u8; 16]> { - unsafe { - if (*self.raw).kind as u32 & raw::GIT_CERT_SSH_MD5 as u32 == 0 { - None - } else { - Some(&(*self.raw).hash_md5) - } + let kind = unsafe { (*self.raw).kind }; + if kind as u32 & raw::GIT_CERT_SSH_MD5 as u32 == 0 { + None + } else { + Some(unsafe { &(*self.raw).hash_md5 }) } } /// Returns the SHA-1 hash of the hostkey, if available. pub fn hash_sha1(&self) -> Option<&[u8; 20]> { - unsafe { - if (*self.raw).kind as u32 & raw::GIT_CERT_SSH_SHA1 as u32 == 0 { - None - } else { - Some(&(*self.raw).hash_sha1) - } + let kind = unsafe { (*self.raw).kind }; + if kind as u32 & raw::GIT_CERT_SSH_SHA1 as u32 == 0 { + None + } else { + Some(unsafe { &(*self.raw).hash_sha1 }) } } /// Returns the SHA-256 hash of the hostkey, if available. pub fn hash_sha256(&self) -> Option<&[u8; 32]> { - unsafe { - if (*self.raw).kind as u32 & raw::GIT_CERT_SSH_SHA256 as u32 == 0 { - None - } else { - Some(&(*self.raw).hash_sha256) - } + let kind = unsafe { (*self.raw).kind }; + if kind as u32 & raw::GIT_CERT_SSH_SHA256 as u32 == 0 { + None + } else { + Some(unsafe { &(*self.raw).hash_sha256 }) } } /// Returns the raw host key. pub fn hostkey(&self) -> Option<&[u8]> { - unsafe { - if (*self.raw).kind & raw::GIT_CERT_SSH_RAW == 0 { - return None; - } - Some(slice::from_raw_parts( + let kind = unsafe { (*self.raw).kind }; + if kind & raw::GIT_CERT_SSH_RAW == 0 { + return None; + } + Some(unsafe { + slice::from_raw_parts( (*self.raw).hostkey as *const u8, (*self.raw).hostkey_len as usize, - )) - } + ) + }) } /// Returns the type of the host key. pub fn hostkey_type(&self) -> Option { - unsafe { - if (*self.raw).kind & raw::GIT_CERT_SSH_RAW == 0 { - return None; - } - let t = match (*self.raw).raw_type { - raw::GIT_CERT_SSH_RAW_TYPE_UNKNOWN => SshHostKeyType::Unknown, - raw::GIT_CERT_SSH_RAW_TYPE_RSA => SshHostKeyType::Rsa, - raw::GIT_CERT_SSH_RAW_TYPE_DSS => SshHostKeyType::Dss, - raw::GIT_CERT_SSH_RAW_TYPE_KEY_ECDSA_256 => SshHostKeyType::Ecdsa256, - raw::GIT_CERT_SSH_RAW_TYPE_KEY_ECDSA_384 => SshHostKeyType::Ecdsa384, - raw::GIT_CERT_SSH_RAW_TYPE_KEY_ECDSA_521 => SshHostKeyType::Ecdsa521, - raw::GIT_CERT_SSH_RAW_TYPE_KEY_ED25519 => SshHostKeyType::Ed255219, - t => panic!("unexpected host key type {:?}", t), - }; - Some(t) + let kind = unsafe { (*self.raw).kind }; + if kind & raw::GIT_CERT_SSH_RAW == 0 { + return None; } + let raw_type = unsafe { (*self.raw).raw_type }; + let t = match raw_type { + raw::GIT_CERT_SSH_RAW_TYPE_UNKNOWN => SshHostKeyType::Unknown, + raw::GIT_CERT_SSH_RAW_TYPE_RSA => SshHostKeyType::Rsa, + raw::GIT_CERT_SSH_RAW_TYPE_DSS => SshHostKeyType::Dss, + raw::GIT_CERT_SSH_RAW_TYPE_KEY_ECDSA_256 => SshHostKeyType::Ecdsa256, + raw::GIT_CERT_SSH_RAW_TYPE_KEY_ECDSA_384 => SshHostKeyType::Ecdsa384, + raw::GIT_CERT_SSH_RAW_TYPE_KEY_ECDSA_521 => SshHostKeyType::Ecdsa521, + raw::GIT_CERT_SSH_RAW_TYPE_KEY_ED25519 => SshHostKeyType::Ed255219, + t => panic!("unexpected host key type {:?}", t), + }; + Some(t) } } From 9537f40611912a853dd765eb9e9d8b19e2a5b6bb Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Mon, 1 Jun 2026 16:34:33 -0700 Subject: [PATCH 06/39] [Unsafety minimization] Reduce `unsafe` blocks in src/cherrypick.rs --- src/cherrypick.rs | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/src/cherrypick.rs b/src/cherrypick.rs index 659b73089b..51005264ba 100644 --- a/src/cherrypick.rs +++ b/src/cherrypick.rs @@ -44,29 +44,35 @@ impl<'cb> CherrypickOptions<'cb> { /// Obtain the raw struct pub fn raw(&mut self) -> raw::git_cherrypick_options { + let mut checkout_opts: raw::git_checkout_options = unsafe { mem::zeroed() }; unsafe { - let mut checkout_opts: raw::git_checkout_options = mem::zeroed(); raw::git_checkout_init_options(&mut checkout_opts, raw::GIT_CHECKOUT_OPTIONS_VERSION); - if let Some(ref mut cb) = self.checkout_builder { - cb.configure(&mut checkout_opts); - } + } + if let Some(ref mut cb) = self.checkout_builder { + unsafe { cb.configure(&mut checkout_opts) }; + } - let mut merge_opts: raw::git_merge_options = mem::zeroed(); + let mut merge_opts: raw::git_merge_options = unsafe { mem::zeroed() }; + unsafe { raw::git_merge_init_options(&mut merge_opts, raw::GIT_MERGE_OPTIONS_VERSION); - if let Some(ref opts) = self.merge_opts { + } + if let Some(ref opts) = self.merge_opts { + unsafe { ptr::copy(opts.raw(), &mut merge_opts, 1); } + } - let mut cherrypick_opts: raw::git_cherrypick_options = mem::zeroed(); + let mut cherrypick_opts: raw::git_cherrypick_options = unsafe { mem::zeroed() }; + unsafe { raw::git_cherrypick_init_options( &mut cherrypick_opts, raw::GIT_CHERRYPICK_OPTIONS_VERSION, ); - cherrypick_opts.mainline = self.mainline; - cherrypick_opts.checkout_opts = checkout_opts; - cherrypick_opts.merge_opts = merge_opts; - - cherrypick_opts } + cherrypick_opts.mainline = self.mainline; + cherrypick_opts.checkout_opts = checkout_opts; + cherrypick_opts.merge_opts = merge_opts; + + cherrypick_opts } } From ae69a70d7ca154232d7c33d67b66a66de44702aa Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Mon, 1 Jun 2026 16:34:33 -0700 Subject: [PATCH 07/39] [Unsafety minimization] Reduce `unsafe` blocks in src/commit.rs --- src/commit.rs | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/src/commit.rs b/src/commit.rs index c3c4104051..860bbc2d66 100644 --- a/src/commit.rs +++ b/src/commit.rs @@ -175,12 +175,9 @@ impl<'repo> Commit<'repo> { /// The second element is the offset, in minutes, of the time zone of the /// committer's preferred time zone. pub fn time(&self) -> Time { - unsafe { - Time::new( - raw::git_commit_time(&*self.raw) as i64, - raw::git_commit_time_offset(&*self.raw) as i32, - ) - } + let time = unsafe { raw::git_commit_time(&*self.raw) } as i64; + let offset = unsafe { raw::git_commit_time_offset(&*self.raw) } as i32; + Time::new(time, offset) } /// Creates a new iterator over the parents of this commit. @@ -309,13 +306,11 @@ impl<'repo> Commit<'repo> { /// /// Use the `parent_ids` iterator to return an iterator over all parents. pub fn parent_id(&self, i: usize) -> Result { - unsafe { - let id = raw::git_commit_parent_id(self.raw, i as libc::c_uint); - if id.is_null() { - Err(Error::from_str("parent index out of bounds")) - } else { - Ok(Binding::from_raw(id)) - } + let id = unsafe { raw::git_commit_parent_id(self.raw, i as libc::c_uint) }; + if id.is_null() { + Err(Error::from_str("parent index out of bounds")) + } else { + Ok(unsafe { Binding::from_raw(id) }) } } From d65370f71c8b60efe7561dadce4b723ed0aa1356 Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Mon, 1 Jun 2026 16:34:33 -0700 Subject: [PATCH 08/39] [Unsafety minimization] Reduce `unsafe` blocks in src/config.rs --- src/config.rs | 38 ++++++++++++++++++-------------------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/src/config.rs b/src/config.rs index b2b51541d1..68729abfd6 100644 --- a/src/config.rs +++ b/src/config.rs @@ -163,8 +163,8 @@ impl Config { ptr::null(), force )); - Ok(()) } + Ok(()) } /// Delete a config variable from the config file with the highest level @@ -173,8 +173,8 @@ impl Config { let name = CString::new(name)?; unsafe { try_call!(raw::git_config_delete_entry(self.raw, name)); - Ok(()) } + Ok(()) } /// Remove multivar config variables in the config file with the highest level (usually the @@ -334,18 +334,16 @@ impl Config { /// ``` pub fn entries(&self, glob: Option<&str>) -> Result, Error> { let mut ret = ptr::null_mut(); - unsafe { - match glob { - Some(s) => { - let s = CString::new(s)?; - try_call!(raw::git_config_iterator_glob_new(&mut ret, &*self.raw, s)); - } - None => { - try_call!(raw::git_config_iterator_new(&mut ret, &*self.raw)); - } + match glob { + Some(s) => { + let s = CString::new(s)?; + unsafe { try_call!(raw::git_config_iterator_glob_new(&mut ret, &*self.raw, s)) }; + } + None => { + unsafe { try_call!(raw::git_config_iterator_new(&mut ret, &*self.raw)) }; } - Ok(Binding::from_raw(ret)) } + Ok(unsafe { Binding::from_raw(ret) }) } /// Iterate over the values of a multivar @@ -558,7 +556,7 @@ impl<'cfg> ConfigEntry<'cfg> { /// Gets the configuration level of this entry. pub fn level(&self) -> ConfigLevel { - unsafe { ConfigLevel::from_raw((*self.raw).level) } + ConfigLevel::from_raw(unsafe { (*self.raw).level }) } /// Depth of includes where this variable was found @@ -606,14 +604,14 @@ impl<'cfg> ConfigEntries<'cfg> { drop(self.current.take()); unsafe { try_call_iter!(raw::git_config_next(&mut raw, self.raw)); - let entry = ConfigEntry { - owned: false, - raw, - _marker: marker::PhantomData, - }; - self.current = Some(entry); - Some(Ok(self.current.as_ref().unwrap())) } + let entry = ConfigEntry { + owned: false, + raw, + _marker: marker::PhantomData, + }; + self.current = Some(entry); + Some(Ok(self.current.as_ref().unwrap())) } /// Calls the given closure for each remaining entry in the iterator. From f1a328b8a64e4ff2f28552b4535e4e2005bbc2e9 Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Mon, 1 Jun 2026 16:34:34 -0700 Subject: [PATCH 09/39] [Unsafety minimization] Reduce `unsafe` blocks in src/cred.rs --- src/cred.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/cred.rs b/src/cred.rs index adf621b5d8..cfec9d9d7a 100644 --- a/src/cred.rs +++ b/src/cred.rs @@ -183,10 +183,9 @@ impl Binding for Cred { impl Drop for Cred { fn drop(&mut self) { if !self.raw.is_null() { - unsafe { - if let Some(f) = (*self.raw).free { - f(self.raw) - } + let free_cb = unsafe { (*self.raw).free }; + if let Some(f) = free_cb { + f(self.raw) } } } From f79faecde0e90407729454cf03b35c8f5a80577a Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Mon, 1 Jun 2026 16:34:34 -0700 Subject: [PATCH 10/39] [Unsafety minimization] Reduce `unsafe` blocks in src/diff.rs --- src/diff.rs | 123 +++++++++++++++++++++++++++------------------------- 1 file changed, 65 insertions(+), 58 deletions(-) diff --git a/src/diff.rs b/src/diff.rs index 38b6ed8256..6f7748d84e 100644 --- a/src/diff.rs +++ b/src/diff.rs @@ -179,8 +179,8 @@ impl<'repo> Diff<'repo> { let print: raw::git_diff_line_cb = Some(print_cb); unsafe { try_call!(raw::git_diff_print(self.raw, format, print, ptr as *mut _)); - Ok(()) } + Ok(()) } /// Loop over all deltas in a diff issuing callbacks. @@ -201,23 +201,23 @@ impl<'repo> Diff<'repo> { line: line_cb, }; let ptr = &mut cbs as *mut _; + let binary_cb_c: raw::git_diff_binary_cb = if cbs.binary.is_some() { + Some(binary_cb_c) + } else { + None + }; + let hunk_cb_c: raw::git_diff_hunk_cb = if cbs.hunk.is_some() { + Some(hunk_cb_c) + } else { + None + }; + let line_cb_c: raw::git_diff_line_cb = if cbs.line.is_some() { + Some(line_cb_c) + } else { + None + }; + let file_cb: raw::git_diff_file_cb = Some(file_cb_c); unsafe { - let binary_cb_c: raw::git_diff_binary_cb = if cbs.binary.is_some() { - Some(binary_cb_c) - } else { - None - }; - let hunk_cb_c: raw::git_diff_hunk_cb = if cbs.hunk.is_some() { - Some(hunk_cb_c) - } else { - None - }; - let line_cb_c: raw::git_diff_line_cb = if cbs.line.is_some() { - Some(line_cb_c) - } else { - None - }; - let file_cb: raw::git_diff_file_cb = Some(file_cb_c); try_call!(raw::git_diff_foreach( self.raw, file_cb, @@ -226,8 +226,8 @@ impl<'repo> Diff<'repo> { line_cb_c, ptr as *mut _ )); - Ok(()) } + Ok(()) } /// Accumulate diff statistics for all patches. @@ -328,21 +328,23 @@ impl Diff<'static> { let data = buffer.as_ptr() as *const c_char; let len = buffer.len(); // NOTE: Doesn't depend on repo, so lifetime can be 'static - unsafe { - #[cfg(not(feature = "unstable-sha256"))] - { - let _ = format; + #[cfg(not(feature = "unstable-sha256"))] + { + let _ = format; + unsafe { try_call!(raw::git_diff_from_buffer(&mut diff, data, len)); } - #[cfg(feature = "unstable-sha256")] - { - let mut opts: raw::git_diff_parse_options = std::mem::zeroed(); - opts.version = raw::GIT_DIFF_PARSE_OPTIONS_VERSION; - opts.oid_type = format.raw(); + } + #[cfg(feature = "unstable-sha256")] + { + let mut opts: raw::git_diff_parse_options = unsafe { std::mem::zeroed() }; + opts.version = raw::GIT_DIFF_PARSE_OPTIONS_VERSION; + opts.oid_type = format.raw(); + unsafe { try_call!(raw::git_diff_from_buffer(&mut diff, data, len, &mut opts)); } - Ok(Diff::from_raw(diff)) } + Ok(unsafe { Diff::from_raw(diff) }) } } @@ -352,20 +354,21 @@ pub extern "C" fn print_cb( line: *const raw::git_diff_line, data: *mut c_void, ) -> c_int { + let r; unsafe { let delta = Binding::from_raw(delta as *mut _); let hunk = Binding::from_raw_opt(hunk); let line = Binding::from_raw(line); - let r = panic::wrap(|| { + r = panic::wrap(|| { let data = data as *mut &mut PrintCb<'_>; (*data)(delta, hunk, line) }); - if r == Some(true) { - raw::GIT_OK - } else { - raw::GIT_EUSER - } + } + if r == Some(true) { + raw::GIT_OK + } else { + raw::GIT_EUSER } } @@ -374,21 +377,22 @@ pub extern "C" fn file_cb_c( progress: f32, data: *mut c_void, ) -> c_int { + let r; unsafe { let delta = Binding::from_raw(delta as *mut _); - let r = panic::wrap(|| { + r = panic::wrap(|| { let cbs = data as *mut DiffCallbacks<'_, '_, '_, '_, '_, '_, '_, '_>; match (*cbs).file { Some(ref mut cb) => cb(delta, progress), None => false, } }); - if r == Some(true) { - raw::GIT_OK - } else { - raw::GIT_EUSER - } + } + if r == Some(true) { + raw::GIT_OK + } else { + raw::GIT_EUSER } } @@ -397,22 +401,23 @@ pub extern "C" fn binary_cb_c( binary: *const raw::git_diff_binary, data: *mut c_void, ) -> c_int { + let r; unsafe { let delta = Binding::from_raw(delta as *mut _); let binary = Binding::from_raw(binary); - let r = panic::wrap(|| { + r = panic::wrap(|| { let cbs = data as *mut DiffCallbacks<'_, '_, '_, '_, '_, '_, '_, '_>; match (*cbs).binary { Some(ref mut cb) => cb(delta, binary), None => false, } }); - if r == Some(true) { - raw::GIT_OK - } else { - raw::GIT_EUSER - } + } + if r == Some(true) { + raw::GIT_OK + } else { + raw::GIT_EUSER } } @@ -421,22 +426,23 @@ pub extern "C" fn hunk_cb_c( hunk: *const raw::git_diff_hunk, data: *mut c_void, ) -> c_int { + let r; unsafe { let delta = Binding::from_raw(delta as *mut _); let hunk = Binding::from_raw(hunk); - let r = panic::wrap(|| { + r = panic::wrap(|| { let cbs = data as *mut DiffCallbacks<'_, '_, '_, '_, '_, '_, '_, '_>; match (*cbs).hunk { Some(ref mut cb) => cb(delta, hunk), None => false, } }); - if r == Some(true) { - raw::GIT_OK - } else { - raw::GIT_EUSER - } + } + if r == Some(true) { + raw::GIT_OK + } else { + raw::GIT_EUSER } } @@ -446,23 +452,24 @@ pub extern "C" fn line_cb_c( line: *const raw::git_diff_line, data: *mut c_void, ) -> c_int { + let r; unsafe { let delta = Binding::from_raw(delta as *mut _); let hunk = Binding::from_raw_opt(hunk); let line = Binding::from_raw(line); - let r = panic::wrap(|| { + r = panic::wrap(|| { let cbs = data as *mut DiffCallbacks<'_, '_, '_, '_, '_, '_, '_, '_>; match (*cbs).line { Some(ref mut cb) => cb(delta, hunk, line), None => false, } }); - if r == Some(true) { - raw::GIT_OK - } else { - raw::GIT_EUSER - } + } + if r == Some(true) { + raw::GIT_OK + } else { + raw::GIT_EUSER } } From 5e202e85c36a4e38e53f5626d877fdef537557ab Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Mon, 1 Jun 2026 16:34:34 -0700 Subject: [PATCH 11/39] [Unsafety minimization] Reduce `unsafe` blocks in src/email.rs --- src/email.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/email.rs b/src/email.rs index d3ebc03842..8b5f46f9f4 100644 --- a/src/email.rs +++ b/src/email.rs @@ -163,8 +163,8 @@ impl Email { Binding::raw(author), opts.raw() )); - Ok(Self { buf }) } + Ok(Self { buf }) } /// Create a diff for a commit in mbox format for sending via email. @@ -177,7 +177,7 @@ impl Email { commit.raw(), opts.raw() )); - Ok(Self { buf }) } + Ok(Self { buf }) } } From 237bb73fa09f4c3e8ac78a5b63d487cc8151e080 Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Mon, 1 Jun 2026 16:34:35 -0700 Subject: [PATCH 12/39] [Unsafety minimization] Reduce `unsafe` blocks in src/error.rs --- src/error.rs | 52 +++++++++++++++++++++++++--------------------------- 1 file changed, 25 insertions(+), 27 deletions(-) diff --git a/src/error.rs b/src/error.rs index 403389a2e4..01e2742c1f 100644 --- a/src/error.rs +++ b/src/error.rs @@ -34,33 +34,31 @@ impl Error { /// call. This code will later be returned from the `code` function. pub fn last_error(code: c_int) -> Error { crate::init(); - unsafe { - // Note that whenever libgit2 returns an error any negative value - // indicates that an error happened. Auxiliary information is - // *usually* in `git_error_last` but unfortunately that's not always - // the case. Sometimes a negative error code is returned from - // libgit2 *without* calling `git_error_set` internally to configure - // the error. - // - // To handle this case and hopefully provide better error messages - // on our end we unconditionally call `git_error_clear` when we're done - // with an error. This is an attempt to clear it as aggressively as - // possible when we can to ensure that error information from one - // api invocation doesn't leak over to the next api invocation. - // - // Additionally if `git_error_last` returns null then we returned a - // canned error out. - let ptr = raw::git_error_last(); - let err = if ptr.is_null() { - let mut error = Error::from_str("an unknown git error occurred"); - error.code = code; - error - } else { - Error::from_raw(code, ptr) - }; - raw::git_error_clear(); - err - } + // Note that whenever libgit2 returns an error any negative value + // indicates that an error happened. Auxiliary information is + // *usually* in `git_error_last` but unfortunately that's not always + // the case. Sometimes a negative error code is returned from + // libgit2 *without* calling `git_error_set` internally to configure + // the error. + // + // To handle this case and hopefully provide better error messages + // on our end we unconditionally call `git_error_clear` when we're done + // with an error. This is an attempt to clear it as aggressively as + // possible when we can to ensure that error information from one + // api invocation doesn't leak over to the next api invocation. + // + // Additionally if `git_error_last` returns null then we returned a + // canned error out. + let ptr = unsafe { raw::git_error_last() }; + let err = if ptr.is_null() { + let mut error = Error::from_str("an unknown git error occurred"); + error.code = code; + error + } else { + unsafe { Error::from_raw(code, ptr) } + }; + unsafe { raw::git_error_clear() }; + err } unsafe fn from_raw(code: c_int, ptr: *const raw::git_error) -> Error { From 1e7529cd793d576c8706d7b6a968d3e578fc1ddf Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Mon, 1 Jun 2026 16:34:35 -0700 Subject: [PATCH 13/39] [Unsafety minimization] Reduce `unsafe` blocks in src/index.rs --- src/index.rs | 267 +++++++++++++++++++++++++-------------------------- 1 file changed, 131 insertions(+), 136 deletions(-) diff --git a/src/index.rs b/src/index.rs index d4d98dbada..25af05260a 100644 --- a/src/index.rs +++ b/src/index.rs @@ -104,21 +104,23 @@ impl Index { pub fn new_ext(format: ObjectFormat) -> Result { crate::init(); let mut raw = ptr::null_mut(); - unsafe { - #[cfg(not(feature = "unstable-sha256"))] - { - let _ = format; + #[cfg(not(feature = "unstable-sha256"))] + { + let _ = format; + unsafe { try_call!(raw::git_index_new(&mut raw)); } - #[cfg(feature = "unstable-sha256")] - { - let mut opts: raw::git_index_options = std::mem::zeroed(); - opts.version = raw::GIT_INDEX_OPTIONS_VERSION; - opts.oid_type = format.raw(); + } + #[cfg(feature = "unstable-sha256")] + { + let mut opts: raw::git_index_options = unsafe { std::mem::zeroed() }; + opts.version = raw::GIT_INDEX_OPTIONS_VERSION; + opts.oid_type = format.raw(); + unsafe { try_call!(raw::git_index_new(&mut raw, &opts)); } - Ok(Binding::from_raw(raw)) } + Ok(unsafe { Binding::from_raw(raw) }) } /// Create a new bare Git index object as a memory representation of the Git @@ -144,21 +146,21 @@ impl Index { let mut raw = ptr::null_mut(); // Normal file path OK (does not need Windows conversion). let index_path = index_path.into_c_string()?; - unsafe { - #[cfg(not(feature = "unstable-sha256"))] - { - let _ = format; + #[cfg(not(feature = "unstable-sha256"))] + { + let _ = format; + unsafe { try_call!(raw::git_index_open(&mut raw, index_path)); } - #[cfg(feature = "unstable-sha256")] - { - let mut opts: raw::git_index_options = std::mem::zeroed(); - opts.version = raw::GIT_INDEX_OPTIONS_VERSION; - opts.oid_type = format.raw(); - try_call!(raw::git_index_open(&mut raw, index_path, &opts)); - } - Ok(Binding::from_raw(raw)) } + #[cfg(feature = "unstable-sha256")] + { + let mut opts: raw::git_index_options = unsafe { std::mem::zeroed() }; + opts.version = raw::GIT_INDEX_OPTIONS_VERSION; + opts.oid_type = format.raw(); + unsafe { try_call!(raw::git_index_open(&mut raw, index_path, &opts)) }; + } + Ok(unsafe { Binding::from_raw(raw) }) } /// Get index on-disk version. @@ -201,30 +203,30 @@ impl Index { flags |= raw::GIT_INDEX_ENTRY_NAMEMASK; } + let raw = raw::git_index_entry { + dev: entry.dev, + ino: entry.ino, + mode: entry.mode, + uid: entry.uid, + gid: entry.gid, + file_size: entry.file_size, + id: unsafe { *entry.id.raw() }, + flags, + flags_extended: entry.flags_extended, + path: path.as_ptr(), + mtime: raw::git_index_time { + seconds: entry.mtime.seconds(), + nanoseconds: entry.mtime.nanoseconds(), + }, + ctime: raw::git_index_time { + seconds: entry.ctime.seconds(), + nanoseconds: entry.ctime.nanoseconds(), + }, + }; unsafe { - let raw = raw::git_index_entry { - dev: entry.dev, - ino: entry.ino, - mode: entry.mode, - uid: entry.uid, - gid: entry.gid, - file_size: entry.file_size, - id: *entry.id.raw(), - flags, - flags_extended: entry.flags_extended, - path: path.as_ptr(), - mtime: raw::git_index_time { - seconds: entry.mtime.seconds(), - nanoseconds: entry.mtime.nanoseconds(), - }, - ctime: raw::git_index_time { - seconds: entry.ctime.seconds(), - nanoseconds: entry.ctime.nanoseconds(), - }, - }; try_call!(raw::git_index_add(self.raw, &raw)); - Ok(()) } + Ok(()) } /// Add or update an index entry from a buffer in memory @@ -258,33 +260,33 @@ impl Index { flags |= raw::GIT_INDEX_ENTRY_NAMEMASK; } + let raw = raw::git_index_entry { + dev: entry.dev, + ino: entry.ino, + mode: entry.mode, + uid: entry.uid, + gid: entry.gid, + file_size: entry.file_size, + id: unsafe { *entry.id.raw() }, + flags, + flags_extended: entry.flags_extended, + path: path.as_ptr(), + mtime: raw::git_index_time { + seconds: entry.mtime.seconds(), + nanoseconds: entry.mtime.nanoseconds(), + }, + ctime: raw::git_index_time { + seconds: entry.ctime.seconds(), + nanoseconds: entry.ctime.nanoseconds(), + }, + }; + + let ptr = data.as_ptr() as *const c_void; + let len = data.len() as size_t; unsafe { - let raw = raw::git_index_entry { - dev: entry.dev, - ino: entry.ino, - mode: entry.mode, - uid: entry.uid, - gid: entry.gid, - file_size: entry.file_size, - id: *entry.id.raw(), - flags, - flags_extended: entry.flags_extended, - path: path.as_ptr(), - mtime: raw::git_index_time { - seconds: entry.mtime.seconds(), - nanoseconds: entry.mtime.nanoseconds(), - }, - ctime: raw::git_index_time { - seconds: entry.ctime.seconds(), - nanoseconds: entry.ctime.nanoseconds(), - }, - }; - - let ptr = data.as_ptr() as *const c_void; - let len = data.len() as size_t; try_call!(raw::git_index_add_frombuffer(self.raw, &raw, ptr, len)); - Ok(()) } + Ok(()) } /// Add or update an index entry from a file on disk @@ -304,8 +306,8 @@ impl Index { let posix_path = path_to_repo_path(path)?; unsafe { try_call!(raw::git_index_add_bypath(self.raw, posix_path)); - Ok(()) } + Ok(()) } /// Add or update index entries matching files in the working directory. @@ -404,13 +406,11 @@ impl Index { /// Get one of the entries in the index by its position. pub fn get(&self, n: usize) -> Option { - unsafe { - let ptr = raw::git_index_get_byindex(self.raw, n as size_t); - if ptr.is_null() { - None - } else { - Some(Binding::from_raw(*ptr)) - } + let ptr = unsafe { raw::git_index_get_byindex(self.raw, n as size_t) }; + if ptr.is_null() { + None + } else { + Some(unsafe { Binding::from_raw(*ptr) }) } } @@ -438,13 +438,11 @@ impl Index { /// Get one of the entries in the index by its path. pub fn get_path(&self, path: &Path, stage: i32) -> Option { let path = path_to_repo_path(path).unwrap(); - unsafe { - let ptr = call!(raw::git_index_get_bypath(self.raw, path, stage as c_int)); - if ptr.is_null() { - None - } else { - Some(Binding::from_raw(*ptr)) - } + let ptr = unsafe { call!(raw::git_index_get_bypath(self.raw, path, stage as c_int)) }; + if ptr.is_null() { + None + } else { + Some(unsafe { Binding::from_raw(*ptr) }) } } @@ -470,22 +468,21 @@ impl Index { self.raw, path )); - - Ok(IndexConflict { - ancestor: match ancestor.is_null() { - false => Some(IndexEntry::from_raw(*ancestor)), - true => None, - }, - our: match our.is_null() { - false => Some(IndexEntry::from_raw(*our)), - true => None, - }, - their: match their.is_null() { - false => Some(IndexEntry::from_raw(*their)), - true => None, - }, - }) } + Ok(IndexConflict { + ancestor: match ancestor.is_null() { + false => Some(unsafe { IndexEntry::from_raw(*ancestor) }), + true => None, + }, + our: match our.is_null() { + false => Some(unsafe { IndexEntry::from_raw(*our) }), + true => None, + }, + their: match their.is_null() { + false => Some(unsafe { IndexEntry::from_raw(*their) }), + true => None, + }, + }) } /// Get the full path to the index file on disk. @@ -690,8 +687,8 @@ impl Index { self.raw, entry_path )); - Ok(at_pos) } + Ok(at_pos) } } @@ -714,30 +711,28 @@ impl IndexEntry { flags |= raw::GIT_INDEX_ENTRY_NAMEMASK; } - unsafe { - let raw = raw::git_index_entry { - dev: self.dev, - ino: self.ino, - mode: self.mode, - uid: self.uid, - gid: self.gid, - file_size: self.file_size, - id: *self.id.raw(), - flags, - flags_extended: self.flags_extended, - path: path.as_ptr(), - mtime: raw::git_index_time { - seconds: self.mtime.seconds(), - nanoseconds: self.mtime.nanoseconds(), - }, - ctime: raw::git_index_time { - seconds: self.ctime.seconds(), - nanoseconds: self.ctime.nanoseconds(), - }, - }; - - Ok((raw, path)) - } + let raw = raw::git_index_entry { + dev: self.dev, + ino: self.ino, + mode: self.mode, + uid: self.uid, + gid: self.gid, + file_size: self.file_size, + id: unsafe { *self.id.raw() }, + flags, + flags_extended: self.flags_extended, + path: path.as_ptr(), + mtime: raw::git_index_time { + seconds: self.mtime.seconds(), + nanoseconds: self.mtime.nanoseconds(), + }, + ctime: raw::git_index_time { + seconds: self.ctime.seconds(), + nanoseconds: self.ctime.nanoseconds(), + }, + }; + + Ok((raw, path)) } } @@ -814,21 +809,21 @@ impl<'index> Iterator for IndexConflicts<'index> { &mut their, self.conflict_iter )); - Some(Ok(IndexConflict { - ancestor: match ancestor.is_null() { - false => Some(IndexEntry::from_raw(*ancestor)), - true => None, - }, - our: match our.is_null() { - false => Some(IndexEntry::from_raw(*our)), - true => None, - }, - their: match their.is_null() { - false => Some(IndexEntry::from_raw(*their)), - true => None, - }, - })) } + Some(Ok(IndexConflict { + ancestor: match ancestor.is_null() { + false => Some(unsafe { IndexEntry::from_raw(*ancestor) }), + true => None, + }, + our: match our.is_null() { + false => Some(unsafe { IndexEntry::from_raw(*our) }), + true => None, + }, + their: match their.is_null() { + false => Some(unsafe { IndexEntry::from_raw(*their) }), + true => None, + }, + })) } } From 7b313907762488a287191459894ce3298fa9a529 Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Mon, 1 Jun 2026 16:34:35 -0700 Subject: [PATCH 14/39] [Unsafety minimization] Reduce `unsafe` blocks in src/indexer.rs --- src/indexer.rs | 53 +++++++++++++++++++++++++------------------------- 1 file changed, 27 insertions(+), 26 deletions(-) diff --git a/src/indexer.rs b/src/indexer.rs index f6efe6ac65..72984582a7 100644 --- a/src/indexer.rs +++ b/src/indexer.rs @@ -151,26 +151,30 @@ impl<'a> Indexer<'a> { let progress_payload = Box::new(OdbPackwriterCb { cb: None }); let progress_payload_ptr = Box::into_raw(progress_payload); + let mut opts = unsafe { mem::zeroed() }; unsafe { - let mut opts = mem::zeroed(); try_call!(raw::git_indexer_options_init( &mut opts, raw::GIT_INDEXER_OPTIONS_VERSION )); - opts.progress_cb = progress_cb; - opts.progress_cb_payload = progress_payload_ptr as *mut c_void; - opts.verify = verify.into(); - - #[cfg(not(feature = "unstable-sha256"))] - { - let _ = format; + } + opts.progress_cb = progress_cb; + opts.progress_cb_payload = progress_payload_ptr as *mut c_void; + opts.verify = verify.into(); + + #[cfg(not(feature = "unstable-sha256"))] + { + let _ = format; + unsafe { try_call!(raw::git_indexer_new(&mut out, path, mode, odb, &mut opts)); } - #[cfg(feature = "unstable-sha256")] - { - opts.mode = mode; - opts.oid_type = format.raw(); - opts.odb = odb; + } + #[cfg(feature = "unstable-sha256")] + { + opts.mode = mode; + opts.oid_type = format.raw(); + opts.odb = odb; + unsafe { try_call!(raw::git_indexer_new(&mut out, path, &mut opts)); } } @@ -191,10 +195,10 @@ impl<'a> Indexer<'a> { pub fn commit(mut self) -> Result { unsafe { try_call!(raw::git_indexer_commit(self.raw, &mut self.progress)); - - let name = CStr::from_ptr(raw::git_indexer_name(self.raw)); - Ok(name.to_str().expect("pack name not utf8").to_owned()) } + + let name = unsafe { CStr::from_ptr(raw::git_indexer_name(self.raw)) }; + Ok(name.to_str().expect("pack name not utf8").to_owned()) } /// The callback through which progress is monitored. Be aware that this is @@ -213,16 +217,13 @@ impl<'a> Indexer<'a> { impl io::Write for Indexer<'_> { fn write(&mut self, buf: &[u8]) -> io::Result { - unsafe { - let ptr = buf.as_ptr() as *mut c_void; - let len = buf.len(); - - let res = raw::git_indexer_append(self.raw, ptr, len, &mut self.progress); - if res < 0 { - Err(io::Error::new(io::ErrorKind::Other, Error::last_error(res))) - } else { - Ok(buf.len()) - } + let ptr = buf.as_ptr() as *mut c_void; + let len = buf.len(); + let res = unsafe { raw::git_indexer_append(self.raw, ptr, len, &mut self.progress) }; + if res < 0 { + Err(io::Error::new(io::ErrorKind::Other, Error::last_error(res))) + } else { + Ok(buf.len()) } } From 6f5463aeb797ccb1294291565fb3ce9cb9572e9d Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Mon, 1 Jun 2026 16:34:36 -0700 Subject: [PATCH 15/39] [Unsafety minimization] Reduce `unsafe` blocks in src/lib.rs --- src/lib.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 79764e4f81..91f253611d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -921,11 +921,12 @@ fn opt_cstr(o: Option) -> Result, Error> { impl ObjectType { /// Convert an object type to its string representation. pub fn str(&self) -> &'static str { + let data; unsafe { let ptr = call!(raw::git_object_type2string(*self)) as *const _; - let data = CStr::from_ptr(ptr).to_bytes(); - str::from_utf8(data).unwrap() + data = CStr::from_ptr(ptr).to_bytes(); } + str::from_utf8(data).unwrap() } /// Determine if the given git_object_t is a valid loose object type. From d987482edbd2443ef6273f4fa22bdd283e44f98d Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Mon, 1 Jun 2026 16:34:36 -0700 Subject: [PATCH 16/39] [Unsafety minimization] Reduce `unsafe` blocks in src/mailmap.rs --- src/mailmap.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mailmap.rs b/src/mailmap.rs index 384406a84e..affb0b2f1a 100644 --- a/src/mailmap.rs +++ b/src/mailmap.rs @@ -74,8 +74,8 @@ impl Mailmap { replace_name, replace_email )); - Ok(()) } + Ok(()) } /// Resolves a signature to its real name and email address. From 49c14ae786c9dc4b0f4032b292e0255ed641bcc8 Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Mon, 1 Jun 2026 16:34:36 -0700 Subject: [PATCH 17/39] [Unsafety minimization] Reduce `unsafe` blocks in src/merge.rs --- src/merge.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/merge.rs b/src/merge.rs index 76a1151dfb..75264315ca 100644 --- a/src/merge.rs +++ b/src/merge.rs @@ -488,12 +488,12 @@ pub fn merge_file( theirs: &MergeFileInput<'_>, opts: Option<&mut MergeFileOptions>, ) -> Result { - unsafe { - let ancestor = ancestor.raw(); - let ours = ours.raw(); - let theirs = theirs.raw(); + let ancestor = ancestor.raw(); + let ours = ours.raw(); + let theirs = theirs.raw(); - let mut ret = mem::zeroed(); + let mut ret = unsafe { mem::zeroed() }; + unsafe { try_call!(raw::git_merge_file( &mut ret, ancestor, From f3bf72abc62d28c1c14029c4cd7c088e8c69bc74 Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Mon, 1 Jun 2026 16:34:37 -0700 Subject: [PATCH 18/39] [Unsafety minimization] Reduce `unsafe` blocks in src/message.rs --- src/message.rs | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/src/message.rs b/src/message.rs index a7041da3ae..3edfd4e5f0 100644 --- a/src/message.rs +++ b/src/message.rs @@ -148,10 +148,8 @@ struct MessageTrailersIterator<'a> { } fn to_raw_tuple(trailers: &MessageTrailers, index: usize) -> (*const c_char, *const c_char) { - unsafe { - let addr = trailers.raw.trailers.wrapping_add(index); - ((*addr).key, (*addr).value) - } + let addr = trailers.raw.trailers.wrapping_add(index); + unsafe { ((*addr).key, (*addr).value) } } /// Borrowed iterator over the UTF-8-encoded trailers. @@ -190,12 +188,10 @@ impl DoubleEndedIterator for MessageTrailersStrsIterator<'_> { } fn to_str_tuple(trailers: &MessageTrailers, index: usize) -> (&str, &str) { - unsafe { - let (rkey, rvalue) = to_raw_tuple(&trailers, index); - let key = CStr::from_ptr(rkey).to_str().unwrap(); - let value = CStr::from_ptr(rvalue).to_str().unwrap(); - (key, value) - } + let (rkey, rvalue) = to_raw_tuple(&trailers, index); + let key = unsafe { CStr::from_ptr(rkey).to_str().unwrap() }; + let value = unsafe { CStr::from_ptr(rvalue).to_str().unwrap() }; + (key, value) } /// Borrowed iterator over the raw (bytes) trailers. @@ -234,12 +230,10 @@ impl DoubleEndedIterator for MessageTrailersBytesIterator<'_> { } fn to_bytes_tuple(trailers: &MessageTrailers, index: usize) -> (&[u8], &[u8]) { - unsafe { - let (rkey, rvalue) = to_raw_tuple(&trailers, index); - let key = CStr::from_ptr(rkey).to_bytes(); - let value = CStr::from_ptr(rvalue).to_bytes(); - (key, value) - } + let (rkey, rvalue) = to_raw_tuple(&trailers, index); + let key = unsafe { CStr::from_ptr(rkey).to_bytes() }; + let value = unsafe { CStr::from_ptr(rvalue).to_bytes() }; + (key, value) } #[cfg(test)] From 9ed3a188b2d6d5f863243387e490becfe4068150 Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Mon, 1 Jun 2026 16:34:37 -0700 Subject: [PATCH 19/39] [Unsafety minimization] Reduce `unsafe` blocks in src/object.rs --- src/object.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/object.rs b/src/object.rs index fcae0066cb..8b74b302ec 100644 --- a/src/object.rs +++ b/src/object.rs @@ -71,11 +71,11 @@ impl<'repo> Object<'repo> { /// result will be unambiguous (at least until new objects are added to the /// repository). pub fn short_id(&self) -> Result { + let buf = Buf::new(); unsafe { - let buf = Buf::new(); try_call!(raw::git_object_short_id(buf.raw(), &*self.raw())); - Ok(buf) } + Ok(buf) } /// Attempt to view this object as a commit. From d0b1e03f1733a67635174aee977d4138c92438dc Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Mon, 1 Jun 2026 16:34:37 -0700 Subject: [PATCH 20/39] [Unsafety minimization] Reduce `unsafe` blocks in src/odb.rs --- src/odb.rs | 116 ++++++++++++++++++++++++++--------------------------- 1 file changed, 56 insertions(+), 60 deletions(-) diff --git a/src/odb.rs b/src/odb.rs index c6900403fd..f6af9c5c13 100644 --- a/src/odb.rs +++ b/src/odb.rs @@ -59,22 +59,24 @@ impl<'repo> Odb<'repo> { /// See [`Odb::new`] for more details. pub fn new_ext<'a>(format: ObjectFormat) -> Result, Error> { crate::init(); - unsafe { - let mut out = ptr::null_mut(); - #[cfg(not(feature = "unstable-sha256"))] - { - let _ = format; + let mut out = ptr::null_mut(); + #[cfg(not(feature = "unstable-sha256"))] + { + let _ = format; + unsafe { try_call!(raw::git_odb_new(&mut out)); } - #[cfg(feature = "unstable-sha256")] - { - let mut opts: raw::git_odb_options = std::mem::zeroed(); - opts.version = raw::GIT_ODB_OPTIONS_VERSION; - opts.oid_type = format.raw(); + } + #[cfg(feature = "unstable-sha256")] + { + let mut opts: raw::git_odb_options = unsafe { std::mem::zeroed() }; + opts.version = raw::GIT_ODB_OPTIONS_VERSION; + opts.oid_type = format.raw(); + unsafe { try_call!(raw::git_odb_new(&mut out, &opts)); } - Ok(Odb::from_raw(out)) } + Ok(unsafe { Odb::from_raw(out) }) } /// Create object database reading stream. @@ -123,18 +125,18 @@ impl<'repo> Odb<'repo> { where C: FnMut(&Oid) -> bool, { + let mut data = ForeachCbData { + callback: &mut callback, + }; + let cb: raw::git_odb_foreach_cb = Some(foreach_cb); unsafe { - let mut data = ForeachCbData { - callback: &mut callback, - }; - let cb: raw::git_odb_foreach_cb = Some(foreach_cb); try_call!(raw::git_odb_foreach( self.raw(), cb, &mut data as *mut _ as *mut _ )); - Ok(()) } + Ok(()) } /// Read an object from the database. @@ -159,9 +161,8 @@ impl<'repo> Odb<'repo> { self.raw, oid.raw() )); - - Ok((size, ObjectType::from_raw(kind_id).unwrap())) } + Ok((size, ObjectType::from_raw(kind_id).unwrap())) } /// Write an object to the database. @@ -214,8 +215,8 @@ impl<'repo> Odb<'repo> { /// Potentially finds an object that starts with the given prefix. pub fn exists_prefix(&self, short_oid: Oid, len: usize) -> Result { + let mut out = crate::util::zeroed_raw_oid(); unsafe { - let mut out = crate::util::zeroed_raw_oid(); try_call!(raw::git_odb_exists_prefix( &mut out, self.raw, @@ -235,17 +236,17 @@ impl<'repo> Odb<'repo> { pub fn refresh(&self) -> Result<(), Error> { unsafe { try_call!(raw::git_odb_refresh(self.raw)); - Ok(()) } + Ok(()) } /// Adds an alternate disk backend to the object database. pub fn add_disk_alternate(&self, path: &str) -> Result<(), Error> { + let path = CString::new(path)?; unsafe { - let path = CString::new(path)?; try_call!(raw::git_odb_add_disk_alternate(self.raw, path)); - Ok(()) } + Ok(()) } /// Create a new mempack backend, and add it to this odb with the given @@ -273,8 +274,8 @@ impl<'repo> Odb<'repo> { &'odb self, priority: i32, ) -> Result, Error> { + let mut mempack = ptr::null_mut(); unsafe { - let mut mempack = ptr::null_mut(); // The mempack backend object in libgit2 is only ever freed by an // odb that has the backend in its list. So to avoid potentially // leaking the mempack backend, this API ensures that the backend @@ -333,11 +334,10 @@ impl<'a> OdbObject<'a> { /// Get the object data. pub fn data(&self) -> &[u8] { + let size = self.len(); unsafe { - let size = self.len(); let ptr: *const u8 = raw::git_odb_object_data(self.raw) as *const u8; - let buffer = slice::from_raw_parts(ptr, size); - return buffer; + slice::from_raw_parts(ptr, size) } } @@ -379,15 +379,13 @@ impl<'repo> Drop for OdbReader<'repo> { impl<'repo> io::Read for OdbReader<'repo> { fn read(&mut self, buf: &mut [u8]) -> io::Result { - unsafe { - let ptr = buf.as_ptr() as *mut c_char; - let len = buf.len(); - let res = raw::git_odb_stream_read(self.raw, ptr, len); - if res < 0 { - Err(io::Error::new(io::ErrorKind::Other, "Read error")) - } else { - Ok(res as _) - } + let ptr = buf.as_ptr() as *mut c_char; + let len = buf.len(); + let res = unsafe { raw::git_odb_stream_read(self.raw, ptr, len) }; + if res < 0 { + Err(io::Error::new(io::ErrorKind::Other, "Read error")) + } else { + Ok(res as _) } } } @@ -440,15 +438,13 @@ impl<'repo> Drop for OdbWriter<'repo> { impl<'repo> io::Write for OdbWriter<'repo> { fn write(&mut self, buf: &[u8]) -> io::Result { - unsafe { - let ptr = buf.as_ptr() as *const c_char; - let len = buf.len(); - let res = raw::git_odb_stream_write(self.raw, ptr, len); - if res < 0 { - Err(io::Error::new(io::ErrorKind::Other, "Write error")) - } else { - Ok(buf.len()) - } + let ptr = buf.as_ptr() as *const c_char; + let len = buf.len(); + let res = unsafe { raw::git_odb_stream_write(self.raw, ptr, len) }; + if res < 0 { + Err(io::Error::new(io::ErrorKind::Other, "Write error")) + } else { + Ok(buf.len()) } } fn flush(&mut self) -> io::Result<()> { @@ -470,18 +466,19 @@ pub struct OdbPackwriter<'repo> { impl<'repo> OdbPackwriter<'repo> { /// Finish writing the packfile pub fn commit(&mut self) -> Result { + let res; unsafe { let writepack = &*self.raw; - let res = match writepack.commit { + res = match writepack.commit { Some(commit) => commit(self.raw, &mut self.progress), None => -1, }; + } - if res < 0 { - Err(Error::last_error(res)) - } else { - Ok(res) - } + if res < 0 { + Err(Error::last_error(res)) + } else { + Ok(res) } } @@ -501,21 +498,20 @@ impl<'repo> OdbPackwriter<'repo> { impl<'repo> io::Write for OdbPackwriter<'repo> { fn write(&mut self, buf: &[u8]) -> io::Result { + let ptr = buf.as_ptr() as *mut c_void; + let len = buf.len(); + let res; unsafe { - let ptr = buf.as_ptr() as *mut c_void; - let len = buf.len(); - let writepack = &*self.raw; - let res = match writepack.append { + res = match writepack.append { Some(append) => append(self.raw, ptr, len, &mut self.progress), None => -1, }; - - if res < 0 { - Err(io::Error::new(io::ErrorKind::Other, "Write error")) - } else { - Ok(buf.len()) - } + } + if res < 0 { + Err(io::Error::new(io::ErrorKind::Other, "Write error")) + } else { + Ok(buf.len()) } } fn flush(&mut self) -> io::Result<()> { From 24bb62a53c5e6d26de5cb0f8fe09033e7517f59a Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Mon, 1 Jun 2026 16:34:38 -0700 Subject: [PATCH 21/39] [Unsafety minimization] Reduce `unsafe` blocks in src/oid.rs --- src/oid.rs | 36 +++++++++++++++++++++--------------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/src/oid.rs b/src/oid.rs index bac03d36bf..8bfca61180 100644 --- a/src/oid.rs +++ b/src/oid.rs @@ -111,13 +111,15 @@ impl Oid { let mut raw = crate::util::zeroed_raw_oid(); let data = s.as_bytes().as_ptr() as *const libc::c_char; let len = s.len() as libc::size_t; - unsafe { - #[cfg(not(feature = "unstable-sha256"))] - { - let _ = format; + #[cfg(not(feature = "unstable-sha256"))] + { + let _ = format; + unsafe { try_call!(raw::git_oid_fromstrn(&mut raw, data, len)); } - #[cfg(feature = "unstable-sha256")] + } + #[cfg(feature = "unstable-sha256")] + unsafe { try_call!(raw::git_oid_fromstrn(&mut raw, data, len, format.raw())); } Ok(Oid { raw }) @@ -192,13 +194,15 @@ impl Oid { let mut out = crate::util::zeroed_raw_oid(); let data = bytes.as_ptr() as *const libc::c_void; - unsafe { - #[cfg(not(feature = "unstable-sha256"))] - { - let _ = format; + #[cfg(not(feature = "unstable-sha256"))] + { + let _ = format; + unsafe { try_call!(raw::git_odb_hash(&mut out, data, bytes.len(), kind.raw())); } - #[cfg(feature = "unstable-sha256")] + } + #[cfg(feature = "unstable-sha256")] + unsafe { try_call!(raw::git_odb_hash( &mut out, data, @@ -236,13 +240,15 @@ impl Oid { let rpath = path.as_ref().into_c_string()?; let mut out = crate::util::zeroed_raw_oid(); - unsafe { - #[cfg(not(feature = "unstable-sha256"))] - { - let _ = format; + #[cfg(not(feature = "unstable-sha256"))] + { + let _ = format; + unsafe { try_call!(raw::git_odb_hashfile(&mut out, rpath, kind.raw())); } - #[cfg(feature = "unstable-sha256")] + } + #[cfg(feature = "unstable-sha256")] + unsafe { try_call!(raw::git_odb_hashfile( &mut out, rpath, From a688a70dc557f76692a78bb47a0803c8d6d2f72b Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Mon, 1 Jun 2026 16:34:38 -0700 Subject: [PATCH 22/39] [Unsafety minimization] Reduce `unsafe` blocks in src/packbuilder.rs --- src/packbuilder.rs | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/packbuilder.rs b/src/packbuilder.rs index 6595f0e396..4f673957c4 100644 --- a/src/packbuilder.rs +++ b/src/packbuilder.rs @@ -257,18 +257,19 @@ impl Binding for PackBuilderStage { } extern "C" fn foreach_c(buf: *const c_void, size: size_t, data: *mut c_void) -> c_int { + let r; unsafe { let buf = slice::from_raw_parts(buf as *const u8, size as usize); - let r = panic::wrap(|| { + r = panic::wrap(|| { let data = data as *mut &mut ForEachCb<'_>; (*data)(buf) }); - if r == Some(true) { - 0 - } else { - -1 - } + } + if r == Some(true) { + 0 + } else { + -1 } } @@ -278,18 +279,19 @@ extern "C" fn progress_c( total: c_uint, data: *mut c_void, ) -> c_int { + let r; unsafe { let stage = Binding::from_raw(stage); - let r = panic::wrap(|| { + r = panic::wrap(|| { let data = data as *mut Box>; (*data)(stage, current, total) }); - if r == Some(true) { - 0 - } else { - -1 - } + } + if r == Some(true) { + 0 + } else { + -1 } } From cf12f363806d24d3034da28ce7e75aaab9002a37 Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Mon, 1 Jun 2026 16:34:39 -0700 Subject: [PATCH 23/39] [Unsafety minimization] Reduce `unsafe` blocks in src/patch.rs --- src/patch.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/patch.rs b/src/patch.rs index d44e87e925..7cea151fe4 100644 --- a/src/patch.rs +++ b/src/patch.rs @@ -205,11 +205,11 @@ impl<'buffers> Patch<'buffers> { /// Print the Patch to text via a callback. pub fn print(&mut self, mut line_cb: &mut LineCb<'_>) -> Result<(), Error> { let ptr = &mut line_cb as *mut _ as *mut c_void; + let cb: raw::git_diff_line_cb = Some(print_cb); unsafe { - let cb: raw::git_diff_line_cb = Some(print_cb); try_call!(raw::git_patch_print(self.raw, cb, ptr)); - Ok(()) } + Ok(()) } /// Get the Patch text as a Buf. From 14d7161cc721bf179f89ba045c95b057ddb691cc Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Mon, 1 Jun 2026 16:34:39 -0700 Subject: [PATCH 24/39] [Unsafety minimization] Reduce `unsafe` blocks in src/pathspec.rs --- src/pathspec.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pathspec.rs b/src/pathspec.rs index 16850dc210..19939ecac6 100644 --- a/src/pathspec.rs +++ b/src/pathspec.rs @@ -47,8 +47,8 @@ impl Pathspec { { crate::init(); let (_a, _b, arr) = crate::util::iter2cstrs_paths(specs)?; + let mut ret = ptr::null_mut(); unsafe { - let mut ret = ptr::null_mut(); try_call!(raw::git_pathspec_new(&mut ret, &arr)); Ok(Binding::from_raw(ret)) } From d79ac4aaaf3e492540250955fa4542ec72490460 Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Mon, 1 Jun 2026 16:34:40 -0700 Subject: [PATCH 25/39] [Unsafety minimization] Reduce `unsafe` blocks in src/rebase.rs --- src/rebase.rs | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/rebase.rs b/src/rebase.rs index 1d88c0cd86..9c89b1133a 100644 --- a/src/rebase.rs +++ b/src/rebase.rs @@ -83,19 +83,21 @@ impl<'cb> RebaseOptions<'cb> { /// Acquire a pointer to the underlying raw options. pub fn raw(&mut self) -> *const raw::git_rebase_options { - unsafe { - if let Some(opts) = self.merge_options.as_mut().take() { + if let Some(opts) = self.merge_options.as_mut().take() { + unsafe { ptr::copy_nonoverlapping(opts.raw(), &mut self.raw.merge_options, 1); } - if let Some(opts) = self.checkout_options.as_mut() { + } + if let Some(opts) = self.checkout_options.as_mut() { + unsafe { opts.configure(&mut self.raw.checkout_options); } - self.raw.rewrite_notes_ref = self - .rewrite_notes_ref - .as_ref() - .map(|s| s.as_ptr()) - .unwrap_or(ptr::null()); } + self.raw.rewrite_notes_ref = self + .rewrite_notes_ref + .as_ref() + .map(|s| s.as_ptr()) + .unwrap_or(ptr::null()); &self.raw } } @@ -129,13 +131,11 @@ impl<'repo> Rebase<'repo> { /// Gets the rebase operation specified by the given index. pub fn nth(&mut self, n: usize) -> Option> { - unsafe { - let op = raw::git_rebase_operation_byindex(self.raw, n); - if op.is_null() { - None - } else { - Some(RebaseOperation::from_raw(op)) - } + let op = unsafe { raw::git_rebase_operation_byindex(self.raw, n) }; + if op.is_null() { + None + } else { + Some(unsafe { RebaseOperation::from_raw(op) }) } } From 9fe8be8e96a3f3e8677c4a99b40f9341addc1e29 Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Mon, 1 Jun 2026 16:34:40 -0700 Subject: [PATCH 26/39] [Unsafety minimization] Reduce `unsafe` blocks in src/reference.rs --- src/reference.rs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/reference.rs b/src/reference.rs index 04ed5cc0e5..1c51873d2a 100644 --- a/src/reference.rs +++ b/src/reference.rs @@ -165,9 +165,9 @@ impl<'repo> Reference<'repo> { refname, flags.bits() )); - let s = &dst[..dst.iter().position(|&a| a == 0).unwrap()]; - Ok(str::from_utf8(s).unwrap().to_owned()) } + let s = &dst[..dst.iter().position(|&a| a == 0).unwrap()]; + Ok(str::from_utf8(s).unwrap().to_owned()) } /// Get access to the underlying raw pointer. @@ -503,15 +503,16 @@ impl<'repo, 'references> Iterator for ReferenceNames<'repo, 'references> { type Item = Result<&'references str, Error>; fn next(&mut self) -> Option> { let mut out = ptr::null(); + let bytes; unsafe { try_call_iter!(raw::git_reference_next_name(&mut out, self.inner.raw)); - let bytes = crate::opt_bytes(self, out).unwrap(); - let s = match str::from_utf8(bytes) { - Ok(s) => s, - Err(e) => return Some(Err(e.into())), - }; - Some(Ok(mem::transmute::<&str, &'references str>(s))) + bytes = crate::opt_bytes(self, out).unwrap(); } + let s = match str::from_utf8(bytes) { + Ok(s) => s, + Err(e) => return Some(Err(e.into())), + }; + Some(Ok(unsafe { mem::transmute::<&str, &'references str>(s) })) } } From def18dd216a4fe09a304de94eb1c4fdb04544cc5 Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Mon, 1 Jun 2026 16:34:41 -0700 Subject: [PATCH 27/39] [Unsafety minimization] Reduce `unsafe` blocks in src/refspec.rs --- src/refspec.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/refspec.rs b/src/refspec.rs index fc28609647..7717ab6787 100644 --- a/src/refspec.rs +++ b/src/refspec.rs @@ -75,29 +75,29 @@ impl<'remote> Refspec<'remote> { /// Transform a reference to its target following the refspec's rules pub fn transform(&self, name: &str) -> Result { let name = CString::new(name).unwrap(); + let buf = Buf::new(); unsafe { - let buf = Buf::new(); try_call!(raw::git_refspec_transform( buf.raw(), self.raw, name.as_ptr() )); - Ok(buf) } + Ok(buf) } /// Transform a target reference to its source reference following the refspec's rules pub fn rtransform(&self, name: &str) -> Result { let name = CString::new(name).unwrap(); + let buf = Buf::new(); unsafe { - let buf = Buf::new(); try_call!(raw::git_refspec_rtransform( buf.raw(), self.raw, name.as_ptr() )); - Ok(buf) } + Ok(buf) } } From 88b9df2447f960b0e883f1b684b0bf344f0cfc9e Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Mon, 1 Jun 2026 16:34:42 -0700 Subject: [PATCH 28/39] [Unsafety minimization] Reduce `unsafe` blocks in src/remote.rs --- src/remote.rs | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/src/remote.rs b/src/remote.rs index 0037b53975..05dcf6ab93 100644 --- a/src/remote.rs +++ b/src/remote.rs @@ -176,11 +176,11 @@ impl<'repo> Remote<'repo> { /// connection to the remote is initiated and it remains available after /// disconnecting. pub fn default_branch(&self) -> Result { + let buf = Buf::new(); unsafe { - let buf = Buf::new(); try_call!(raw::git_remote_default_branch(buf.raw(), self.raw)); - Ok(buf) } + Ok(buf) } /// Get the remote's object format (hash algorithm). @@ -402,18 +402,20 @@ impl<'repo> Remote<'repo> { mem::size_of::>(), mem::size_of::<*const raw::git_remote_head>() ); - if base.is_null() { - // We cannot use slice::from_raw_parts() since that requires - // that the pointer be non-null, but that is fine since the size - // should be zero - if size != 0 { - return Err(Error::from_str(&format!( - "git_remote_ls() set a null pointer for a list of size {}", - size - ))); - } - return Ok(&[]); + } + if base.is_null() { + // We cannot use slice::from_raw_parts() since that requires + // that the pointer be non-null, but that is fine since the size + // should be zero + if size != 0 { + return Err(Error::from_str(&format!( + "git_remote_ls() set a null pointer for a list of size {}", + size + ))); } + return Ok(&[]); + } + unsafe { let slice = slice::from_raw_parts(base as *const _, size as usize); Ok(mem::transmute::< &[*const raw::git_remote_head], From 67ee62f93519a99d898b1c9fd9b1e8b33dedb45c Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Mon, 1 Jun 2026 16:34:42 -0700 Subject: [PATCH 29/39] [Unsafety minimization] Reduce `unsafe` blocks in src/remote_callbacks.rs --- src/remote_callbacks.rs | 134 ++++++++++++++++++++-------------------- 1 file changed, 67 insertions(+), 67 deletions(-) diff --git a/src/remote_callbacks.rs b/src/remote_callbacks.rs index 2df2e7b015..e4b39b0b85 100644 --- a/src/remote_callbacks.rs +++ b/src/remote_callbacks.rs @@ -259,48 +259,48 @@ impl<'a> Binding for RemoteCallbacks<'a> { } fn raw(&self) -> raw::git_remote_callbacks { + let mut callbacks: raw::git_remote_callbacks = unsafe { mem::zeroed() }; unsafe { - let mut callbacks: raw::git_remote_callbacks = mem::zeroed(); assert_eq!( raw::git_remote_init_callbacks(&mut callbacks, raw::GIT_REMOTE_CALLBACKS_VERSION), 0 ); - if self.progress.is_some() { - callbacks.transfer_progress = Some(transfer_progress_cb); - } - if self.credentials.is_some() { - callbacks.credentials = Some(credentials_cb); - } - if self.sideband_progress.is_some() { - callbacks.sideband_progress = Some(sideband_progress_cb); - } - if self.certificate_check.is_some() { - callbacks.certificate_check = Some(certificate_check_cb); - } - if self.push_update_reference.is_some() { - callbacks.push_update_reference = Some(push_update_reference_cb); - } - if self.push_progress.is_some() { - callbacks.push_transfer_progress = Some(push_transfer_progress_cb); - } - if self.pack_progress.is_some() { - callbacks.pack_progress = Some(pack_progress_cb); - } - if self.update_tips.is_some() { - let f: extern "C" fn( - *const c_char, - *const raw::git_oid, - *const raw::git_oid, - *mut c_void, - ) -> c_int = update_tips_cb; - callbacks.update_tips = Some(f); - } - if self.push_negotiation.is_some() { - callbacks.push_negotiation = Some(push_negotiation_cb); - } - callbacks.payload = self as *const _ as *mut _; - callbacks } + if self.progress.is_some() { + callbacks.transfer_progress = Some(transfer_progress_cb); + } + if self.credentials.is_some() { + callbacks.credentials = Some(credentials_cb); + } + if self.sideband_progress.is_some() { + callbacks.sideband_progress = Some(sideband_progress_cb); + } + if self.certificate_check.is_some() { + callbacks.certificate_check = Some(certificate_check_cb); + } + if self.push_update_reference.is_some() { + callbacks.push_update_reference = Some(push_update_reference_cb); + } + if self.push_progress.is_some() { + callbacks.push_transfer_progress = Some(push_transfer_progress_cb); + } + if self.pack_progress.is_some() { + callbacks.pack_progress = Some(pack_progress_cb); + } + if self.update_tips.is_some() { + let f: extern "C" fn( + *const c_char, + *const raw::git_oid, + *const raw::git_oid, + *mut c_void, + ) -> c_int = update_tips_cb; + callbacks.update_tips = Some(f); + } + if self.push_negotiation.is_some() { + callbacks.push_negotiation = Some(push_negotiation_cb); + } + callbacks.payload = self as *const _ as *mut _; + callbacks } } @@ -311,41 +311,41 @@ extern "C" fn credentials_cb( allowed_types: c_uint, payload: *mut c_void, ) -> c_int { - unsafe { - let ok = panic::wrap(|| { - let payload = &mut *(payload as *mut RemoteCallbacks<'_>); - let callback = payload - .credentials - .as_mut() - .ok_or(raw::GIT_PASSTHROUGH as c_int)?; - *ret = ptr::null_mut(); - let url = str::from_utf8(CStr::from_ptr(url).to_bytes()) - .map_err(|_| raw::GIT_PASSTHROUGH as c_int)?; - let username_from_url = match crate::opt_bytes(&url, username_from_url) { - Some(username) => { - Some(str::from_utf8(username).map_err(|_| raw::GIT_PASSTHROUGH as c_int)?) - } - None => None, - }; - - let cred_type = CredentialType::from_bits_truncate(allowed_types as u32); - - callback(url, username_from_url, cred_type).map_err(|e| e.raw_set_git_error()) - }); - match ok { - Some(Ok(cred)) => { - // Turns out it's a memory safety issue if we pass through any - // and all credentials into libgit2 - if allowed_types & (cred.credtype() as c_uint) != 0 { + let ok = panic::wrap(|| unsafe { + let payload = &mut *(payload as *mut RemoteCallbacks<'_>); + let callback = payload + .credentials + .as_mut() + .ok_or(raw::GIT_PASSTHROUGH as c_int)?; + *ret = ptr::null_mut(); + let url = str::from_utf8(CStr::from_ptr(url).to_bytes()) + .map_err(|_| raw::GIT_PASSTHROUGH as c_int)?; + let username_from_url = match crate::opt_bytes(&url, username_from_url) { + Some(username) => { + Some(str::from_utf8(username).map_err(|_| raw::GIT_PASSTHROUGH as c_int)?) + } + None => None, + }; + + let cred_type = CredentialType::from_bits_truncate(allowed_types as u32); + + callback(url, username_from_url, cred_type).map_err(|e| e.raw_set_git_error()) + }); + match ok { + Some(Ok(cred)) => { + // Turns out it's a memory safety issue if we pass through any + // and all credentials into libgit2 + if allowed_types & (cred.credtype() as c_uint) != 0 { + unsafe { *ret = cred.unwrap(); - 0 - } else { - raw::GIT_PASSTHROUGH as c_int } + 0 + } else { + raw::GIT_PASSTHROUGH as c_int } - Some(Err(e)) => e, - None => -1, } + Some(Err(e)) => e, + None => -1, } } From 8d4efed72fcfad60b1659dbe58768ae38e6bea0f Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Mon, 1 Jun 2026 16:34:43 -0700 Subject: [PATCH 30/39] [Unsafety minimization] Reduce `unsafe` blocks in src/repo.rs --- src/repo.rs | 271 ++++++++++++++++++++++++++-------------------------- 1 file changed, 134 insertions(+), 137 deletions(-) diff --git a/src/repo.rs b/src/repo.rs index 7a61be161a..f2aebce569 100644 --- a/src/repo.rs +++ b/src/repo.rs @@ -385,13 +385,16 @@ impl Repository { flags: 0, }; let spec = CString::new(spec)?; + let to; + let from; + let mode; unsafe { try_call!(raw::git_revparse(&mut raw, self.raw, spec)); - let to = Binding::from_raw_opt(raw.to); - let from = Binding::from_raw_opt(raw.from); - let mode = RevparseMode::from_bits_truncate(raw.flags as u32); - Ok(Revspec::from_objects(from, to, mode)) + to = Binding::from_raw_opt(raw.to); + from = Binding::from_raw_opt(raw.from); + mode = RevparseMode::from_bits_truncate(raw.flags as u32); } + Ok(Revspec::from_objects(from, to, mode)) } /// Find a single object, as specified by a revision string. @@ -510,13 +513,11 @@ impl Repository { /// /// If this repository is bare, then `None` is returned. pub fn workdir(&self) -> Option<&Path> { - unsafe { - let ptr = raw::git_repository_workdir(self.raw); - if ptr.is_null() { - None - } else { - Some(util::bytes2path(CStr::from_ptr(ptr).to_bytes())) - } + let ptr = unsafe { raw::git_repository_workdir(self.raw) }; + if ptr.is_null() { + None + } else { + Some(util::bytes2path(unsafe { CStr::from_ptr(ptr) }.to_bytes())) } } @@ -562,37 +563,37 @@ impl Repository { /// Set the active namespace for this repository as a byte array. pub fn set_namespace_bytes(&self, namespace: &[u8]) -> Result<(), Error> { + let namespace = CString::new(namespace)?; unsafe { - let namespace = CString::new(namespace)?; try_call!(raw::git_repository_set_namespace(self.raw, namespace)); - Ok(()) } + Ok(()) } /// Remove the active namespace for this repository. pub fn remove_namespace(&self) -> Result<(), Error> { unsafe { try_call!(raw::git_repository_set_namespace(self.raw, ptr::null())); - Ok(()) } + Ok(()) } /// Retrieves the Git merge message. /// Remember to remove the message when finished. pub fn message(&self) -> Result { + let buf = Buf::new(); unsafe { - let buf = Buf::new(); try_call!(raw::git_repository_message(buf.raw(), self.raw)); - Ok(str::from_utf8(&buf).unwrap().to_string()) } + Ok(str::from_utf8(&buf).unwrap().to_string()) } /// Remove the Git merge message. pub fn remove_message(&self) -> Result<(), Error> { unsafe { try_call!(raw::git_repository_message_remove(self.raw)); - Ok(()) } + Ok(()) } /// List all remotes for a given repository @@ -867,13 +868,11 @@ impl Repository { /// Determines whether the repository HEAD is detached. pub fn head_detached(&self) -> Result { - unsafe { - let value = raw::git_repository_head_detached(self.raw); - match value { - 0 => Ok(false), - 1 => Ok(true), - _ => Err(Error::last_error(value)), - } + let value = unsafe { raw::git_repository_head_detached(self.raw) }; + match value { + 0 => Ok(false), + 1 => Ok(true), + _ => Err(Error::last_error(value)), } } @@ -949,12 +948,12 @@ impl Repository { } let mut ret = Vec::new(); + let mut data = Data { + repo: self, + ret: &mut ret, + }; + let cb: raw::git_submodule_cb = Some(append); unsafe { - let mut data = Data { - repo: self, - ret: &mut ret, - }; - let cb: raw::git_submodule_cb = Some(append); try_call!(raw::git_submodule_foreach( self.raw, cb, @@ -1401,8 +1400,8 @@ impl Repository { parents.len() as size_t, parent_ptrs.as_mut_ptr() )); - Ok(buf) } + Ok(buf) } /// Create a commit object from the given buffer and signature @@ -1457,8 +1456,8 @@ impl Repository { commit_id.raw() as *mut _, signature_field )); - Ok((signature, content)) } + Ok((signature, content)) } /// Lookup a reference to one of the commits in a repository. @@ -1981,12 +1980,12 @@ impl Repository { /// The tree builder can be used to create or modify trees in memory and /// write them as tree objects to the database. pub fn treebuilder(&self, tree: Option<&Tree<'_>>) -> Result, Error> { + let mut ret = ptr::null_mut(); + let tree = match tree { + Some(tree) => tree.raw(), + None => ptr::null_mut(), + }; unsafe { - let mut ret = ptr::null_mut(); - let tree = match tree { - Some(tree) => tree.raw(), - None => ptr::null_mut(), - }; try_call!(raw::git_treebuilder_new(&mut ret, self.raw, tree)); Ok(Binding::from_raw(ret)) } @@ -2115,8 +2114,8 @@ impl Repository { let name = CString::new(name)?; unsafe { try_call!(raw::git_tag_delete(self.raw, name)); - Ok(()) } + Ok(()) } /// Get a list with all the tags in the repository. @@ -2127,18 +2126,18 @@ impl Repository { strings: ptr::null_mut(), count: 0, }; - unsafe { - match pattern { - Some(s) => { - let s = CString::new(s)?; + match pattern { + Some(s) => { + let s = CString::new(s)?; + unsafe { try_call!(raw::git_tag_list_match(&mut arr, s, self.raw)); } - None => { - try_call!(raw::git_tag_list(&mut arr, self.raw)); - } } - Ok(Binding::from_raw(arr)) + None => unsafe { + try_call!(raw::git_tag_list(&mut arr, self.raw)); + }, } + Ok(unsafe { Binding::from_raw(arr) }) } /// Iterate over all tags, calling the callback `cb` on each. @@ -2251,6 +2250,11 @@ impl Repository { merge_opts: Option<&mut MergeOptions>, checkout_opts: Option<&mut CheckoutBuilder<'_>>, ) -> Result<(), Error> { + let mut commit_ptrs = annotated_commits + .iter() + .map(|c| c.raw() as *const raw::git_annotated_commit) + .collect::>(); + unsafe { let mut raw_checkout_opts = mem::zeroed(); try_call!(raw::git_checkout_init_options( @@ -2261,11 +2265,6 @@ impl Repository { c.configure(&mut raw_checkout_opts); } - let mut commit_ptrs = annotated_commits - .iter() - .map(|c| c.raw() as *const raw::git_annotated_commit) - .collect::>(); - try_call!(raw::git_merge( self.raw, commit_ptrs.as_mut_ptr(), @@ -2340,13 +2339,13 @@ impl Repository { &self, their_heads: &[&AnnotatedCommit<'_>], ) -> Result<(MergeAnalysis, MergePreference), Error> { + let mut raw_merge_analysis = 0 as raw::git_merge_analysis_t; + let mut raw_merge_preference = 0 as raw::git_merge_preference_t; + let mut their_heads = their_heads + .iter() + .map(|v| v.raw() as *const _) + .collect::>(); unsafe { - let mut raw_merge_analysis = 0 as raw::git_merge_analysis_t; - let mut raw_merge_preference = 0 as raw::git_merge_preference_t; - let mut their_heads = their_heads - .iter() - .map(|v| v.raw() as *const _) - .collect::>(); try_call!(raw::git_merge_analysis( &mut raw_merge_analysis, &mut raw_merge_preference, @@ -2354,11 +2353,11 @@ impl Repository { their_heads.as_mut_ptr() as *mut _, their_heads.len() )); - Ok(( - MergeAnalysis::from_bits_truncate(raw_merge_analysis as u32), - MergePreference::from_bits_truncate(raw_merge_preference as u32), - )) } + Ok(( + MergeAnalysis::from_bits_truncate(raw_merge_analysis as u32), + MergePreference::from_bits_truncate(raw_merge_preference as u32), + )) } /// Analyzes the given branch(es) and determines the opportunities for @@ -2368,13 +2367,13 @@ impl Repository { our_ref: &Reference<'_>, their_heads: &[&AnnotatedCommit<'_>], ) -> Result<(MergeAnalysis, MergePreference), Error> { + let mut raw_merge_analysis = 0 as raw::git_merge_analysis_t; + let mut raw_merge_preference = 0 as raw::git_merge_preference_t; + let mut their_heads = their_heads + .iter() + .map(|v| v.raw() as *const _) + .collect::>(); unsafe { - let mut raw_merge_analysis = 0 as raw::git_merge_analysis_t; - let mut raw_merge_preference = 0 as raw::git_merge_preference_t; - let mut their_heads = their_heads - .iter() - .map(|v| v.raw() as *const _) - .collect::>(); try_call!(raw::git_merge_analysis_for_ref( &mut raw_merge_analysis, &mut raw_merge_preference, @@ -2383,11 +2382,11 @@ impl Repository { their_heads.as_mut_ptr() as *mut _, their_heads.len() )); - Ok(( - MergeAnalysis::from_bits_truncate(raw_merge_analysis as u32), - MergePreference::from_bits_truncate(raw_merge_preference as u32), - )) } + Ok(( + MergeAnalysis::from_bits_truncate(raw_merge_analysis as u32), + MergePreference::from_bits_truncate(raw_merge_preference as u32), + )) } /// Initializes a rebase operation to rebase the changes in `branch` @@ -2524,8 +2523,8 @@ impl Repository { committer.raw(), id.raw() )); - Ok(()) } + Ok(()) } /// Create a revwalk that can be used to traverse the commit graph. @@ -2701,9 +2700,9 @@ impl Repository { /// other as its upstream, the ahead and behind values will be what git /// would report for the branches. pub fn graph_ahead_behind(&self, local: Oid, upstream: Oid) -> Result<(usize, usize), Error> { + let mut ahead: size_t = 0; + let mut behind: size_t = 0; unsafe { - let mut ahead: size_t = 0; - let mut behind: size_t = 0; try_call!(raw::git_graph_ahead_behind( &mut ahead, &mut behind, @@ -2711,8 +2710,8 @@ impl Repository { local.raw(), upstream.raw() )); - Ok((ahead as usize, behind as usize)) } + Ok((ahead as usize, behind as usize)) } /// Determine if a commit is the descendant of another commit @@ -2720,14 +2719,14 @@ impl Repository { /// Note that a commit is not considered a descendant of itself, in contrast /// to `git merge-base --is-ancestor`. pub fn graph_descendant_of(&self, commit: Oid, ancestor: Oid) -> Result { - unsafe { - let rv = try_call!(raw::git_graph_descendant_of( + let rv = unsafe { + try_call!(raw::git_graph_descendant_of( self.raw(), commit.raw(), ancestor.raw() - )); - Ok(rv != 0) - } + )) + }; + Ok(rv != 0) } /// Read the reflog for the given reference @@ -2828,27 +2827,27 @@ impl Repository { line: line_cb, }; let ptr = &mut cbs as *mut _; + let file_cb_c: raw::git_diff_file_cb = if cbs.file.is_some() { + Some(file_cb_c) + } else { + None + }; + let binary_cb_c: raw::git_diff_binary_cb = if cbs.binary.is_some() { + Some(binary_cb_c) + } else { + None + }; + let hunk_cb_c: raw::git_diff_hunk_cb = if cbs.hunk.is_some() { + Some(hunk_cb_c) + } else { + None + }; + let line_cb_c: raw::git_diff_line_cb = if cbs.line.is_some() { + Some(line_cb_c) + } else { + None + }; unsafe { - let file_cb_c: raw::git_diff_file_cb = if cbs.file.is_some() { - Some(file_cb_c) - } else { - None - }; - let binary_cb_c: raw::git_diff_binary_cb = if cbs.binary.is_some() { - Some(binary_cb_c) - } else { - None - }; - let hunk_cb_c: raw::git_diff_hunk_cb = if cbs.hunk.is_some() { - Some(hunk_cb_c) - } else { - None - }; - let line_cb_c: raw::git_diff_line_cb = if cbs.line.is_some() { - Some(line_cb_c) - } else { - None - }; try_call!(raw::git_diff_blobs( old_blob.map(|s| s.raw()), old_as_path, @@ -2861,8 +2860,8 @@ impl Repository { line_cb_c, ptr as *mut _ )); - Ok(()) } + Ok(()) } /// Create a diff with the difference between two tree objects. @@ -3062,10 +3061,10 @@ impl Repository { message: Option<&str>, flags: Option, ) -> Result { + let mut raw_oid = crate::util::zeroed_raw_oid(); + let message = crate::opt_cstr(message)?; + let flags = flags.unwrap_or_else(StashFlags::empty); unsafe { - let mut raw_oid = crate::util::zeroed_raw_oid(); - let message = crate::opt_cstr(message)?; - let flags = flags.unwrap_or_else(StashFlags::empty); try_call!(raw::git_stash_save( &mut raw_oid, self.raw(), @@ -3082,8 +3081,8 @@ impl Repository { &mut self, opts: Option<&mut StashSaveOptions<'_>>, ) -> Result { + let mut raw_oid = crate::util::zeroed_raw_oid(); unsafe { - let mut raw_oid = crate::util::zeroed_raw_oid(); let opts = opts.map(|opts| opts.raw()); try_call!(raw::git_stash_save_with_opts( &mut raw_oid, @@ -3100,11 +3099,11 @@ impl Repository { index: usize, opts: Option<&mut StashApplyOptions<'_>>, ) -> Result<(), Error> { + let opts = opts.map(|opts| opts.raw()); unsafe { - let opts = opts.map(|opts| opts.raw()); try_call!(raw::git_stash_apply(self.raw(), index, opts)); - Ok(()) } + Ok(()) } /// Loop over all the stashed states and issue a callback for each one. @@ -3114,26 +3113,26 @@ impl Repository { where C: FnMut(usize, &str, &Oid) -> bool, { + let mut data = StashCbData { + callback: &mut callback, + }; + let cb: raw::git_stash_cb = Some(stash_cb); unsafe { - let mut data = StashCbData { - callback: &mut callback, - }; - let cb: raw::git_stash_cb = Some(stash_cb); try_call!(raw::git_stash_foreach( self.raw(), cb, &mut data as *mut _ as *mut _ )); - Ok(()) } + Ok(()) } /// Remove a single stashed state from the stash list. pub fn stash_drop(&mut self, index: usize) -> Result<(), Error> { unsafe { try_call!(raw::git_stash_drop(self.raw(), index)); - Ok(()) } + Ok(()) } /// Apply a single stashed state from the stash list and remove it from the list if successful. @@ -3142,11 +3141,11 @@ impl Repository { index: usize, opts: Option<&mut StashApplyOptions<'_>>, ) -> Result<(), Error> { + let opts = opts.map(|opts| opts.raw()); unsafe { - let opts = opts.map(|opts| opts.raw()); try_call!(raw::git_stash_pop(self.raw(), index, opts)); - Ok(()) } + Ok(()) } /// Add ignore rules for a repository. @@ -3195,9 +3194,8 @@ impl Repository { }; unsafe { try_call!(raw::git_cherrypick(self.raw(), commit.raw(), ptr_raw_opts)); - - Ok(()) } + Ok(()) } /// Create an index of uncommitted changes, representing the result of @@ -3226,22 +3224,22 @@ impl Repository { /// Find the remote name of a remote-tracking branch pub fn branch_remote_name(&self, refname: &str) -> Result { let refname = CString::new(refname)?; + let buf = Buf::new(); unsafe { - let buf = Buf::new(); try_call!(raw::git_branch_remote_name(buf.raw(), self.raw, refname)); - Ok(buf) } + Ok(buf) } /// Retrieves the name of the reference supporting the remote tracking branch, /// given the name of a local branch reference. pub fn branch_upstream_name(&self, refname: &str) -> Result { let refname = CString::new(refname)?; + let buf = Buf::new(); unsafe { - let buf = Buf::new(); try_call!(raw::git_branch_upstream_name(buf.raw(), self.raw, refname)); - Ok(buf) } + Ok(buf) } /// Retrieve the name of the upstream remote of a local branch. @@ -3249,15 +3247,15 @@ impl Repository { /// `refname` must be in the form `refs/heads/{branch_name}` pub fn branch_upstream_remote(&self, refname: &str) -> Result { let refname = CString::new(refname)?; + let buf = Buf::new(); unsafe { - let buf = Buf::new(); try_call!(raw::git_branch_upstream_remote( buf.raw(), self.raw, refname )); - Ok(buf) } + Ok(buf) } /// Retrieve the upstream merge of a local branch, @@ -3266,11 +3264,11 @@ impl Repository { /// `refname` must be in the form `refs/heads/{branch_name}` pub fn branch_upstream_merge(&self, refname: &str) -> Result { let refname = CString::new(refname)?; + let buf = Buf::new(); unsafe { - let buf = Buf::new(); try_call!(raw::git_branch_upstream_merge(buf.raw(), self.raw, refname)); - Ok(buf) } + Ok(buf) } /// Apply a Diff to the given repo, making changes directly in the working directory, the index, or both. @@ -3287,9 +3285,8 @@ impl Repository { location.raw(), options.map(|s| s.raw()).unwrap_or(ptr::null()) )); - - Ok(()) } + Ok(()) } /// Apply a Diff to the provided tree, and return the resulting Index. @@ -3325,8 +3322,8 @@ impl Repository { }; unsafe { try_call!(raw::git_revert(self.raw(), commit.raw(), ptr_raw_opts)); - Ok(()) } + Ok(()) } /// Reverts the given commit against the given "our" commit, @@ -3423,18 +3420,18 @@ impl Repository { where C: FnMut(&Oid) -> bool, { + let mut data = MergeheadForeachCbData { + callback: &mut callback, + }; + let cb: raw::git_repository_mergehead_foreach_cb = Some(mergehead_foreach_cb); unsafe { - let mut data = MergeheadForeachCbData { - callback: &mut callback, - }; - let cb: raw::git_repository_mergehead_foreach_cb = Some(mergehead_foreach_cb); try_call!(raw::git_repository_mergehead_foreach( self.raw(), cb, &mut data as *mut _ as *mut _ )); - Ok(()) } + Ok(()) } /// Invoke 'callback' for each entry in the given FETCH_HEAD file. @@ -3449,18 +3446,18 @@ impl Repository { where C: FnMut(&str, &[u8], &Oid, bool) -> bool, { + let mut data = FetchheadForeachCbData { + callback: &mut callback, + }; + let cb: raw::git_repository_fetchhead_foreach_cb = Some(fetchhead_foreach_cb); unsafe { - let mut data = FetchheadForeachCbData { - callback: &mut callback, - }; - let cb: raw::git_repository_fetchhead_foreach_cb = Some(fetchhead_foreach_cb); try_call!(raw::git_repository_fetchhead_foreach( self.raw(), cb, &mut data as *mut _ as *mut _ )); - Ok(()) } + Ok(()) } } From 53b0f6065e39492b1ca0b1f43ef259593c131f62 Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Mon, 1 Jun 2026 16:34:43 -0700 Subject: [PATCH 31/39] [Unsafety minimization] Reduce `unsafe` blocks in src/signature.rs --- src/signature.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/signature.rs b/src/signature.rs index 362a6f4465..5346150b82 100644 --- a/src/signature.rs +++ b/src/signature.rs @@ -89,10 +89,8 @@ impl<'a> Signature<'a> { /// Convert a signature of any lifetime into an owned signature with a /// static lifetime. pub fn to_owned(&self) -> Signature<'static> { - unsafe { - let me = mem::transmute::<&Signature<'a>, &Signature<'static>>(self); - me.clone() - } + let me = unsafe { mem::transmute::<&Signature<'a>, &Signature<'static>>(self) }; + me.clone() } } From 3f35d6d84533be415721a6c96f496a236d114ead Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Mon, 1 Jun 2026 16:34:44 -0700 Subject: [PATCH 32/39] [Unsafety minimization] Reduce `unsafe` blocks in src/stash.rs --- src/stash.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/stash.rs b/src/stash.rs index ea898e46ba..cd9ee36f58 100644 --- a/src/stash.rs +++ b/src/stash.rs @@ -135,8 +135,8 @@ impl<'cb> StashApplyOptions<'cb> { /// Pointer to a raw git_stash_apply_options pub fn raw(&mut self) -> &raw::git_stash_apply_options { - unsafe { - if let Some(opts) = self.checkout_options.as_mut() { + if let Some(opts) = self.checkout_options.as_mut() { + unsafe { opts.configure(&mut self.raw_opts.checkout_options); } } From 7fe185eed3951aba847f79994277f6e8806238a8 Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Mon, 1 Jun 2026 16:34:44 -0700 Subject: [PATCH 33/39] [Unsafety minimization] Reduce `unsafe` blocks in src/status.rs --- src/status.rs | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/status.rs b/src/status.rs index 89a22e190a..a8a1bf4f0b 100644 --- a/src/status.rs +++ b/src/status.rs @@ -68,15 +68,13 @@ impl Default for StatusOptions { impl StatusOptions { /// Creates a new blank set of status options. pub fn new() -> StatusOptions { - unsafe { - let mut raw = mem::zeroed(); - let r = raw::git_status_init_options(&mut raw, raw::GIT_STATUS_OPTIONS_VERSION); - assert_eq!(r, 0); - StatusOptions { - raw, - pathspec: Vec::new(), - ptrs: Vec::new(), - } + let mut raw = unsafe { mem::zeroed() }; + let r = unsafe { raw::git_status_init_options(&mut raw, raw::GIT_STATUS_OPTIONS_VERSION) }; + assert_eq!(r, 0); + StatusOptions { + raw, + pathspec: Vec::new(), + ptrs: Vec::new(), } } From d3b31142a55f6cf8568185f056c3d59aa2d3441b Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Mon, 1 Jun 2026 16:34:45 -0700 Subject: [PATCH 34/39] [Unsafety minimization] Reduce `unsafe` blocks in src/submodule.rs --- src/submodule.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/submodule.rs b/src/submodule.rs index e38d6f895f..20b0b4532e 100644 --- a/src/submodule.rs +++ b/src/submodule.rs @@ -42,9 +42,9 @@ impl<'repo> Submodule<'repo> { &mut self, opts: Option<&mut SubmoduleUpdateOptions<'_>>, ) -> Result { + let mut raw_repo = ptr::null_mut(); unsafe { let raw_opts = opts.map(|o| o.raw()); - let mut raw_repo = ptr::null_mut(); try_call!(raw::git_submodule_clone( &mut raw_repo, self.raw, @@ -150,8 +150,8 @@ impl<'repo> Submodule<'repo> { /// use_gitlink: Should the workdir contain a gitlink to the repo in /// .git/modules vs. repo directly in workdir. pub fn repo_init(&mut self, use_gitlink: bool) -> Result { + let mut raw_repo = ptr::null_mut(); unsafe { - let mut raw_repo = ptr::null_mut(); try_call!(raw::git_submodule_repo_init( &mut raw_repo, self.raw, From 4abca51ada60ae03cbf0e25636cd50ce563efb02 Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Mon, 1 Jun 2026 16:34:45 -0700 Subject: [PATCH 35/39] [Unsafety minimization] Reduce `unsafe` blocks in src/tag.rs --- src/tag.rs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/tag.rs b/src/tag.rs index d60fd23e8e..dfa2a78242 100644 --- a/src/tag.rs +++ b/src/tag.rs @@ -74,13 +74,11 @@ impl<'repo> Tag<'repo> { /// /// If the author is unspecified, then `None` is returned. pub fn tagger(&self) -> Option> { - unsafe { - let ptr = raw::git_tag_tagger(&*self.raw); - if ptr.is_null() { - None - } else { - Some(signature::from_raw_const(self, ptr)) - } + let ptr = unsafe { raw::git_tag_tagger(&*self.raw) }; + if ptr.is_null() { + None + } else { + Some(unsafe { signature::from_raw_const(self, ptr) }) } } From ac8fdb120504713dafb1997d3465ffae8d54a614 Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Mon, 1 Jun 2026 16:34:46 -0700 Subject: [PATCH 36/39] [Unsafety minimization] Reduce `unsafe` blocks in src/transport.rs --- src/transport.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/transport.rs b/src/transport.rs index b1ca3f8b80..085f5397db 100644 --- a/src/transport.rs +++ b/src/transport.rs @@ -182,8 +182,8 @@ impl Transport { ) -> c_int { unsafe { *out = ptr as *mut raw::git_smart_subtransport; - 0 } + 0 } } } From e3bda9c7c280ca2fce66f32fb9167384441a5eb7 Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Mon, 1 Jun 2026 16:34:46 -0700 Subject: [PATCH 37/39] [Unsafety minimization] Reduce `unsafe` blocks in src/tree.rs --- src/tree.rs | 50 +++++++++++++++++++++----------------------------- 1 file changed, 21 insertions(+), 29 deletions(-) diff --git a/src/tree.rs b/src/tree.rs index fc70f4670d..f6953ffe20 100644 --- a/src/tree.rs +++ b/src/tree.rs @@ -123,41 +123,37 @@ impl<'repo> Tree<'repo> { C: FnMut(&str, &TreeEntry<'_>) -> T, T: Into, { + let mut data = TreeWalkCbData { + callback: &mut callback, + }; unsafe { - let mut data = TreeWalkCbData { - callback: &mut callback, - }; try_call!(raw::git_tree_walk( self.raw(), mode as raw::git_treewalk_mode, treewalk_cb::, &mut data as *mut _ as *mut c_void )); - Ok(()) } + Ok(()) } /// Lookup a tree entry by SHA value. pub fn get_id(&self, id: Oid) -> Option> { - unsafe { - let ptr = raw::git_tree_entry_byid(&*self.raw(), &*id.raw()); - if ptr.is_null() { - None - } else { - Some(entry_from_raw_const(ptr)) - } + let ptr = unsafe { raw::git_tree_entry_byid(&*self.raw(), &*id.raw()) }; + if ptr.is_null() { + None + } else { + Some(unsafe { entry_from_raw_const(ptr) }) } } /// Lookup a tree entry by its position in the tree pub fn get(&self, n: usize) -> Option> { - unsafe { - let ptr = raw::git_tree_entry_byindex(&*self.raw(), n as libc::size_t); - if ptr.is_null() { - None - } else { - Some(entry_from_raw_const(ptr)) - } + let ptr = unsafe { raw::git_tree_entry_byindex(&*self.raw(), n as libc::size_t) }; + if ptr.is_null() { + None + } else { + Some(unsafe { entry_from_raw_const(ptr) }) } } @@ -171,13 +167,11 @@ impl<'repo> Tree<'repo> { /// This allows for non-UTF-8 filenames. pub fn get_name_bytes(&self, filename: &[u8]) -> Option> { let filename = CString::new(filename).unwrap(); - unsafe { - let ptr = call!(raw::git_tree_entry_byname(&*self.raw(), filename)); - if ptr.is_null() { - None - } else { - Some(entry_from_raw_const(ptr)) - } + let ptr = unsafe { call!(raw::git_tree_entry_byname(&*self.raw(), filename)) }; + if ptr.is_null() { + None + } else { + Some(unsafe { entry_from_raw_const(ptr) }) } } @@ -331,10 +325,8 @@ impl<'tree> TreeEntry<'tree> { /// /// This will use the `Clone::clone` implementation under the hood. pub fn to_owned(&self) -> TreeEntry<'static> { - unsafe { - let me = mem::transmute::<&TreeEntry<'tree>, &TreeEntry<'static>>(self); - me.clone() - } + let me = unsafe { mem::transmute::<&TreeEntry<'tree>, &TreeEntry<'static>>(self) }; + me.clone() } } From dbf808ecd795ee664810959f59b5f1b383c81490 Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Mon, 1 Jun 2026 16:34:47 -0700 Subject: [PATCH 38/39] [Unsafety minimization] Reduce `unsafe` blocks in src/treebuilder.rs --- src/treebuilder.rs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/treebuilder.rs b/src/treebuilder.rs index dce5511af0..a6d9c8d4a0 100644 --- a/src/treebuilder.rs +++ b/src/treebuilder.rs @@ -45,13 +45,11 @@ impl<'repo> TreeBuilder<'repo> { P: IntoCString, { let filename = filename.into_c_string()?; - unsafe { - let ret = raw::git_treebuilder_get(self.raw, filename.as_ptr()); - if ret.is_null() { - Ok(None) - } else { - Ok(Some(tree::entry_from_raw_const(ret))) - } + let ret = unsafe { raw::git_treebuilder_get(self.raw, filename.as_ptr()) }; + if ret.is_null() { + Ok(None) + } else { + Ok(Some(unsafe { tree::entry_from_raw_const(ret) })) } } @@ -106,8 +104,8 @@ impl<'repo> TreeBuilder<'repo> { let cb: raw::git_treebuilder_filter_cb = Some(filter_cb); unsafe { try_call!(raw::git_treebuilder_filter(self.raw, cb, ptr as *mut _)); - panic::check(); } + panic::check(); Ok(()) } From abeb635fdcc58ea86b8a1f814f9470a848fe328d Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Mon, 1 Jun 2026 16:34:47 -0700 Subject: [PATCH 39/39] [Unsafety minimization] Reduce `unsafe` blocks in src/worktree.rs --- src/worktree.rs | 43 +++++++++++++++++++++---------------------- 1 file changed, 21 insertions(+), 22 deletions(-) diff --git a/src/worktree.rs b/src/worktree.rs index f0d98c94a9..0a328124e1 100644 --- a/src/worktree.rs +++ b/src/worktree.rs @@ -106,16 +106,15 @@ impl Worktree { /// Checks if worktree is locked pub fn is_locked(&self) -> Result { let buf = Buf::new(); - unsafe { - match try_call!(raw::git_worktree_is_locked(buf.raw(), self.raw)) { - 0 => Ok(WorktreeLockStatus::Unlocked), - _ => { - let v = buf.to_vec(); - Ok(WorktreeLockStatus::Locked(match v.len() { - 0 => None, - _ => Some(String::from_utf8(v).unwrap()), - })) - } + let locked = unsafe { try_call!(raw::git_worktree_is_locked(buf.raw(), self.raw)) }; + match locked { + 0 => Ok(WorktreeLockStatus::Unlocked), + _ => { + let v = buf.to_vec(); + Ok(WorktreeLockStatus::Locked(match v.len() { + 0 => None, + _ => Some(String::from_utf8(v).unwrap()), + })) } } } @@ -132,13 +131,13 @@ impl Worktree { /// Checks if the worktree is prunable pub fn is_prunable(&self, opts: Option<&mut WorktreePruneOptions>) -> Result { - unsafe { - let rv = try_call!(raw::git_worktree_is_prunable( + let rv = unsafe { + try_call!(raw::git_worktree_is_prunable( self.raw, opts.map(|o| o.raw()) - )); - Ok(rv != 0) - } + )) + }; + Ok(rv != 0) } } @@ -147,16 +146,16 @@ impl<'a> WorktreeAddOptions<'a> { /// /// By default this will not lock the worktree pub fn new() -> WorktreeAddOptions<'a> { + let mut raw = unsafe { mem::zeroed() }; unsafe { - let mut raw = mem::zeroed(); assert_eq!( raw::git_worktree_add_options_init(&mut raw, raw::GIT_WORKTREE_ADD_OPTIONS_VERSION), 0 ); - WorktreeAddOptions { - raw, - _marker: marker::PhantomData, - } + } + WorktreeAddOptions { + raw, + _marker: marker::PhantomData, } } @@ -197,8 +196,8 @@ impl WorktreePruneOptions { /// By defaults this will prune only worktrees that are no longer valid /// unlocked and not checked out pub fn new() -> WorktreePruneOptions { + let mut raw = unsafe { mem::zeroed() }; unsafe { - let mut raw = mem::zeroed(); assert_eq!( raw::git_worktree_prune_options_init( &mut raw, @@ -206,8 +205,8 @@ impl WorktreePruneOptions { ), 0 ); - WorktreePruneOptions { raw } } + WorktreePruneOptions { raw } } /// Controls whether valid (still existing on the filesystem) worktrees