libgit: add higher-level libgit crate
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 <steadmon@google.com> Signed-off-by: Josh Steadmon <steadmon@google.com> Signed-off-by: Calvin Wan <calvinwan@google.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>maint
parent
d76eb0dccc
commit
65c10aa8d5
|
@ -250,4 +250,5 @@ Release/
|
||||||
/git.VC.db
|
/git.VC.db
|
||||||
*.dSYM
|
*.dSYM
|
||||||
/contrib/buildsystems/out
|
/contrib/buildsystems/out
|
||||||
|
/contrib/libgit-rs/target
|
||||||
/contrib/libgit-sys/target
|
/contrib/libgit-sys/target
|
||||||
|
|
12
Makefile
12
Makefile
|
@ -417,7 +417,7 @@ include shared.mak
|
||||||
# programs in oss-fuzz/.
|
# programs in oss-fuzz/.
|
||||||
#
|
#
|
||||||
# Define INCLUDE_LIBGIT_RS if you want `make all` and `make test` to build and
|
# 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 ===
|
# === Optional library: libintl ===
|
||||||
#
|
#
|
||||||
|
@ -3742,7 +3742,7 @@ clean: profile-clean coverage-clean cocciclean
|
||||||
$(RM) $(htmldocs).tar.gz $(manpages).tar.gz
|
$(RM) $(htmldocs).tar.gz $(manpages).tar.gz
|
||||||
$(MAKE) -C Documentation/ clean
|
$(MAKE) -C Documentation/ clean
|
||||||
$(RM) Documentation/GIT-EXCLUDED-PROGRAMS
|
$(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/partial_symbol_export.o
|
||||||
$(RM) contrib/libgit-sys/hidden_symbol_export.o
|
$(RM) contrib/libgit-sys/hidden_symbol_export.o
|
||||||
$(RM) contrib/libgit-sys/libgitpub.a
|
$(RM) contrib/libgit-sys/libgitpub.a
|
||||||
|
@ -3908,14 +3908,14 @@ build-unit-tests: $(UNIT_TEST_PROGS) $(CLAR_TEST_PROG)
|
||||||
unit-tests: $(UNIT_TEST_PROGS) $(CLAR_TEST_PROG) t/helper/test-tool$X
|
unit-tests: $(UNIT_TEST_PROGS) $(CLAR_TEST_PROG) t/helper/test-tool$X
|
||||||
$(MAKE) -C t/ unit-tests
|
$(MAKE) -C t/ unit-tests
|
||||||
|
|
||||||
.PHONY: libgit-sys
|
.PHONY: libgit-sys libgit-rs
|
||||||
libgit-sys:
|
libgit-sys libgit-rs:
|
||||||
$(QUIET)(\
|
$(QUIET)(\
|
||||||
cd contrib/libgit-sys && \
|
cd contrib/$@ && \
|
||||||
cargo build \
|
cargo build \
|
||||||
)
|
)
|
||||||
ifdef INCLUDE_LIBGIT_RS
|
ifdef INCLUDE_LIBGIT_RS
|
||||||
all:: libgit-sys
|
all:: libgit-sys libgit-rs
|
||||||
endif
|
endif
|
||||||
|
|
||||||
LIBGIT_PUB_OBJS += contrib/libgit-sys/public_symbol_export.o
|
LIBGIT_PUB_OBJS += contrib/libgit-sys/public_symbol_export.o
|
||||||
|
|
|
@ -0,0 +1,77 @@
|
||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 3
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "autocfg"
|
||||||
|
version = "1.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cc"
|
||||||
|
version = "1.1.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "57b6a275aa2903740dc87da01c62040406b8812552e97129a63ea8850a17c6e6"
|
||||||
|
dependencies = [
|
||||||
|
"shlex",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libc"
|
||||||
|
version = "0.2.158"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libgit"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
"libgit-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libgit-sys"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
"libz-sys",
|
||||||
|
"make-cmd",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libz-sys"
|
||||||
|
version = "1.1.20"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d2d16453e800a8cf6dd2fc3eb4bc99b786a9b90c663b8559a5b1a041bf89e472"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
"libc",
|
||||||
|
"pkg-config",
|
||||||
|
"vcpkg",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "make-cmd"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a8ca8afbe8af1785e09636acb5a41e08a765f5f0340568716c18a8700ba3c0d3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pkg-config"
|
||||||
|
version = "0.3.30"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "shlex"
|
||||||
|
version = "1.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "vcpkg"
|
||||||
|
version = "0.2.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
|
|
@ -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/Z47jgK-oMjFRSslr@tapette.crustytoothpaste.net/
|
||||||
|
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
path = "src/lib.rs"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
libgit-sys = { version = "0.1.0", path = "../libgit-sys" }
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
autocfg = "1.4.0"
|
|
@ -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).
|
|
@ -0,0 +1,4 @@
|
||||||
|
pub fn main() {
|
||||||
|
let ac = autocfg::new();
|
||||||
|
ac.emit_has_path("std::ffi::c_char");
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
pub mod config;
|
|
@ -0,0 +1,2 @@
|
||||||
|
[trace2]
|
||||||
|
eventNesting = 1
|
|
@ -0,0 +1,2 @@
|
||||||
|
[trace2]
|
||||||
|
eventTarget = 1
|
|
@ -0,0 +1,2 @@
|
||||||
|
[trace2]
|
||||||
|
eventNesting = 3
|
|
@ -1,3 +1,5 @@
|
||||||
|
use std::ffi::c_void;
|
||||||
|
|
||||||
#[cfg(has_std__ffi__c_char)]
|
#[cfg(has_std__ffi__c_char)]
|
||||||
use std::ffi::{c_char, c_int};
|
use std::ffi::{c_char, c_int};
|
||||||
|
|
||||||
|
@ -19,6 +21,8 @@ pub struct libgit_config_set {
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
pub fn free(ptr: *mut c_void);
|
||||||
|
|
||||||
pub fn libgit_user_agent() -> *const c_char;
|
pub fn libgit_user_agent() -> *const c_char;
|
||||||
pub fn libgit_user_agent_sanitized() -> *const c_char;
|
pub fn libgit_user_agent_sanitized() -> *const c_char;
|
||||||
|
|
||||||
|
|
|
@ -178,12 +178,17 @@ perf:
|
||||||
.PHONY: pre-clean $(T) aggregate-results clean valgrind perf \
|
.PHONY: pre-clean $(T) aggregate-results clean valgrind perf \
|
||||||
check-chainlint clean-chainlint test-chainlint $(UNIT_TESTS)
|
check-chainlint clean-chainlint test-chainlint $(UNIT_TESTS)
|
||||||
|
|
||||||
.PHONY: libgit-sys-test
|
.PHONY: libgit-sys-test libgit-rs-test
|
||||||
libgit-sys-test:
|
libgit-sys-test:
|
||||||
$(QUIET)(\
|
$(QUIET)(\
|
||||||
cd ../contrib/libgit-sys && \
|
cd ../contrib/libgit-sys && \
|
||||||
cargo test \
|
cargo test \
|
||||||
)
|
)
|
||||||
|
libgit-rs-test:
|
||||||
|
$(QUIET)(\
|
||||||
|
cd ../contrib/libgit-rs && \
|
||||||
|
cargo test \
|
||||||
|
)
|
||||||
ifdef INCLUDE_LIBGIT_RS
|
ifdef INCLUDE_LIBGIT_RS
|
||||||
all:: libgit-sys-test
|
all:: libgit-sys-test libgit-rs-test
|
||||||
endif
|
endif
|
||||||
|
|
Loading…
Reference in New Issue