Skip to content

Commit

Permalink
libgit: add higher-level libgit crate
Browse files Browse the repository at this point in the history
The C functions exported by libgit-sys do not provide an idiomatic Rust
interface. To make it easier to use these functions via Rust, add a
higher-level "libgit" crate, that wraps the lower-level configset API
with an interface that is more Rust-y.

This combination of $X and $X-sys crates is a common pattern for FFI in
Rust, as documented in "The Cargo Book" [1].

[1] https://doc.rust-lang.org/cargo/reference/build-scripts.html#-sys-packages

Co-authored-by: Josh Steadmon <[email protected]>
Signed-off-by: Josh Steadmon <[email protected]>
Signed-off-by: Calvin Wan <[email protected]>
Signed-off-by: Junio C Hamano <[email protected]>
  • Loading branch information
2 people authored and gitster committed Jan 28, 2025
1 parent 31d5ab2 commit 370189f
Show file tree
Hide file tree
Showing 13 changed files with 245 additions and 6 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -250,4 +250,5 @@ Release/
/git.VC.db
*.dSYM
/contrib/buildsystems/out
/contrib/libgit-rs/target
/contrib/libgit-sys/target
13 changes: 9 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -417,7 +417,7 @@ include shared.mak
# programs in oss-fuzz/.
#
# Define INCLUDE_LIBGIT_RS if you want `make all` and `make test` to build and
# test the Rust crate in contrib/libgit-sys.
# test the Rust crates in contrib/libgit-sys and contrib/libgit-rs.
#
# === Optional library: libintl ===
#
Expand Down Expand Up @@ -3741,7 +3741,7 @@ clean: profile-clean coverage-clean cocciclean
$(RM) $(htmldocs).tar.gz $(manpages).tar.gz
$(MAKE) -C Documentation/ clean
$(RM) Documentation/GIT-EXCLUDED-PROGRAMS
$(RM) -r contrib/libgit-sys/target
$(RM) -r contrib/libgit-sys/target contrib/libgit-rs/target
$(RM) contrib/libgit-sys/partial_symbol_export.o
$(RM) contrib/libgit-sys/hidden_symbol_export.o
$(RM) contrib/libgit-sys/libgitpub.a
Expand Down Expand Up @@ -3907,14 +3907,19 @@ build-unit-tests: $(UNIT_TEST_PROGS) $(CLAR_TEST_PROG)
unit-tests: $(UNIT_TEST_PROGS) $(CLAR_TEST_PROG) t/helper/test-tool$X
$(MAKE) -C t/ unit-tests

.PHONY: libgit-sys
.PHONY: libgit-sys libgit-rs
libgit-sys:
$(QUIET)(\
cd contrib/libgit-sys && \
cargo build \
)
libgit-rs:
$(QUIET)(\
cd contrib/libgit-rs && \
cargo build \
)
ifdef INCLUDE_LIBGIT_RS
all:: libgit-sys
all:: libgit-sys libgit-rs
endif

contrib/libgit-sys/partial_symbol_export.o: contrib/libgit-sys/public_symbol_export.o libgit.a reftable/libreftable.a xdiff/lib.a
Expand Down
77 changes: 77 additions & 0 deletions contrib/libgit-rs/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 17 additions & 0 deletions contrib/libgit-rs/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
[package]
name = "libgit"
version = "0.1.0"
edition = "2021"
build = "build.rs"
rust-version = "1.63" # TODO: Once we hit 1.84 or newer, we may want to remove Cargo.lock from
# version control. See https://lore.kernel.org/git/[email protected]/


[lib]
path = "src/lib.rs"

[dependencies]
libgit-sys = { version = "0.1.0", path = "../libgit-sys" }

[build-dependencies]
autocfg = "1.4.0"
13 changes: 13 additions & 0 deletions contrib/libgit-rs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# libgit-rs

Proof-of-concept Git bindings for Rust.

```toml
[dependencies]
libgit = "0.1.0"
```

## Rust version requirements

libgit-rs should support Rust versions at least as old as the version included
in Debian stable (currently 1.63).
4 changes: 4 additions & 0 deletions contrib/libgit-rs/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
pub fn main() {
let ac = autocfg::new();
ac.emit_has_path("std::ffi::c_char");
}
106 changes: 106 additions & 0 deletions contrib/libgit-rs/src/config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
use std::ffi::{c_void, CStr, CString};
use std::path::Path;

#[cfg(has_std__ffi__c_char)]
use std::ffi::{c_char, c_int};

#[cfg(not(has_std__ffi__c_char))]
#[allow(non_camel_case_types)]
type c_char = i8;

#[cfg(not(has_std__ffi__c_char))]
#[allow(non_camel_case_types)]
type c_int = i32;

use libgit_sys::*;

/// A ConfigSet is an in-memory cache for config-like files such as `.gitmodules` or `.gitconfig`.
/// It does not support all config directives; notably, it will not process `include` or
/// `includeIf` directives (but it will store them so that callers can choose whether and how to
/// handle them).
pub struct ConfigSet(*mut libgit_config_set);
impl ConfigSet {
/// Allocate a new ConfigSet
pub fn new() -> Self {
unsafe { ConfigSet(libgit_configset_alloc()) }
}

/// Load the given files into the ConfigSet; conflicting directives in later files will
/// override those given in earlier files.
pub fn add_files(&mut self, files: &[&Path]) {
for file in files {
let pstr = file.to_str().expect("Invalid UTF-8");
let rs = CString::new(pstr).expect("Couldn't convert to CString");
unsafe {
libgit_configset_add_file(self.0, rs.as_ptr());
}
}
}

/// Load the value for the given key and attempt to parse it as an i32. Dies with a fatal error
/// if the value cannot be parsed. Returns None if the key is not present.
pub fn get_int(&mut self, key: &str) -> Option<i32> {
let key = CString::new(key).expect("Couldn't convert to CString");
let mut val: c_int = 0;
unsafe {
if libgit_configset_get_int(self.0, key.as_ptr(), &mut val as *mut c_int) != 0 {
return None;
}
}

Some(val.into())
}

/// Clones the value for the given key. Dies with a fatal error if the value cannot be
/// converted to a String. Returns None if the key is not present.
pub fn get_string(&mut self, key: &str) -> Option<String> {
let key = CString::new(key).expect("Couldn't convert key to CString");
let mut val: *mut c_char = std::ptr::null_mut();
unsafe {
if libgit_configset_get_string(self.0, key.as_ptr(), &mut val as *mut *mut c_char) != 0
{
return None;
}
let borrowed_str = CStr::from_ptr(val);
let owned_str =
String::from(borrowed_str.to_str().expect("Couldn't convert val to str"));
free(val as *mut c_void); // Free the xstrdup()ed pointer from the C side
Some(owned_str)
}
}
}

impl Default for ConfigSet {
fn default() -> Self {
Self::new()
}
}

impl Drop for ConfigSet {
fn drop(&mut self) {
unsafe {
libgit_configset_free(self.0);
}
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn load_configs_via_configset() {
let mut cs = ConfigSet::new();
cs.add_files(&[
Path::new("testdata/config1"),
Path::new("testdata/config2"),
Path::new("testdata/config3"),
]);
// ConfigSet retrieves correct value
assert_eq!(cs.get_int("trace2.eventTarget"), Some(1));
// ConfigSet respects last config value set
assert_eq!(cs.get_int("trace2.eventNesting"), Some(3));
// ConfigSet returns None for missing key
assert_eq!(cs.get_string("foo.bar"), None);
}
}
1 change: 1 addition & 0 deletions contrib/libgit-rs/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod config;
2 changes: 2 additions & 0 deletions contrib/libgit-rs/testdata/config1
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[trace2]
eventNesting = 1
2 changes: 2 additions & 0 deletions contrib/libgit-rs/testdata/config2
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[trace2]
eventTarget = 1
2 changes: 2 additions & 0 deletions contrib/libgit-rs/testdata/config3
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[trace2]
eventNesting = 3
4 changes: 4 additions & 0 deletions contrib/libgit-sys/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::ffi::c_void;

#[cfg(has_std__ffi__c_char)]
use std::ffi::{c_char, c_int};

Expand All @@ -19,6 +21,8 @@ pub struct libgit_config_set {
}

extern "C" {
pub fn free(ptr: *mut c_void);

pub fn libgit_user_agent() -> *const c_char;
pub fn libgit_user_agent_sanitized() -> *const c_char;

Expand Down
9 changes: 7 additions & 2 deletions t/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -178,12 +178,17 @@ perf:
.PHONY: pre-clean $(T) aggregate-results clean valgrind perf \
check-chainlint clean-chainlint test-chainlint $(UNIT_TESTS)

.PHONY: libgit-sys-test
.PHONY: libgit-sys-test libgit-rs-test
libgit-sys-test:
$(QUIET)(\
cd ../contrib/libgit-sys && \
cargo test \
)
libgit-rs-test:
$(QUIET)(\
cd ../contrib/libgit-rs && \
cargo test \
)
ifdef INCLUDE_LIBGIT_RS
all:: libgit-sys-test
all:: libgit-sys-test libgit-rs-test
endif

0 comments on commit 370189f

Please sign in to comment.