Git fork

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>

authored by

Calvin Wan
Josh Steadmon
and committed by
Junio C Hamano
65c10aa8 d76eb0dc

+242 -8
+1
.gitignore
··· 250 250 /git.VC.db 251 251 *.dSYM 252 252 /contrib/buildsystems/out 253 + /contrib/libgit-rs/target 253 254 /contrib/libgit-sys/target
+6 -6
Makefile
··· 417 417 # programs in oss-fuzz/. 418 418 # 419 419 # Define INCLUDE_LIBGIT_RS if you want `make all` and `make test` to build and 420 - # test the Rust crate in contrib/libgit-sys. 420 + # test the Rust crates in contrib/libgit-sys and contrib/libgit-rs. 421 421 # 422 422 # === Optional library: libintl === 423 423 # ··· 3742 3742 $(RM) $(htmldocs).tar.gz $(manpages).tar.gz 3743 3743 $(MAKE) -C Documentation/ clean 3744 3744 $(RM) Documentation/GIT-EXCLUDED-PROGRAMS 3745 - $(RM) -r contrib/libgit-sys/target 3745 + $(RM) -r contrib/libgit-sys/target contrib/libgit-rs/target 3746 3746 $(RM) contrib/libgit-sys/partial_symbol_export.o 3747 3747 $(RM) contrib/libgit-sys/hidden_symbol_export.o 3748 3748 $(RM) contrib/libgit-sys/libgitpub.a ··· 3908 3908 unit-tests: $(UNIT_TEST_PROGS) $(CLAR_TEST_PROG) t/helper/test-tool$X 3909 3909 $(MAKE) -C t/ unit-tests 3910 3910 3911 - .PHONY: libgit-sys 3912 - libgit-sys: 3911 + .PHONY: libgit-sys libgit-rs 3912 + libgit-sys libgit-rs: 3913 3913 $(QUIET)(\ 3914 - cd contrib/libgit-sys && \ 3914 + cd contrib/$@ && \ 3915 3915 cargo build \ 3916 3916 ) 3917 3917 ifdef INCLUDE_LIBGIT_RS 3918 - all:: libgit-sys 3918 + all:: libgit-sys libgit-rs 3919 3919 endif 3920 3920 3921 3921 LIBGIT_PUB_OBJS += contrib/libgit-sys/public_symbol_export.o
+77
contrib/libgit-rs/Cargo.lock
··· 1 + # This file is automatically @generated by Cargo. 2 + # It is not intended for manual editing. 3 + version = 3 4 + 5 + [[package]] 6 + name = "autocfg" 7 + version = "1.4.0" 8 + source = "registry+https://github.com/rust-lang/crates.io-index" 9 + checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" 10 + 11 + [[package]] 12 + name = "cc" 13 + version = "1.1.15" 14 + source = "registry+https://github.com/rust-lang/crates.io-index" 15 + checksum = "57b6a275aa2903740dc87da01c62040406b8812552e97129a63ea8850a17c6e6" 16 + dependencies = [ 17 + "shlex", 18 + ] 19 + 20 + [[package]] 21 + name = "libc" 22 + version = "0.2.158" 23 + source = "registry+https://github.com/rust-lang/crates.io-index" 24 + checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" 25 + 26 + [[package]] 27 + name = "libgit" 28 + version = "0.1.0" 29 + dependencies = [ 30 + "autocfg", 31 + "libgit-sys", 32 + ] 33 + 34 + [[package]] 35 + name = "libgit-sys" 36 + version = "0.1.0" 37 + dependencies = [ 38 + "autocfg", 39 + "libz-sys", 40 + "make-cmd", 41 + ] 42 + 43 + [[package]] 44 + name = "libz-sys" 45 + version = "1.1.20" 46 + source = "registry+https://github.com/rust-lang/crates.io-index" 47 + checksum = "d2d16453e800a8cf6dd2fc3eb4bc99b786a9b90c663b8559a5b1a041bf89e472" 48 + dependencies = [ 49 + "cc", 50 + "libc", 51 + "pkg-config", 52 + "vcpkg", 53 + ] 54 + 55 + [[package]] 56 + name = "make-cmd" 57 + version = "0.1.0" 58 + source = "registry+https://github.com/rust-lang/crates.io-index" 59 + checksum = "a8ca8afbe8af1785e09636acb5a41e08a765f5f0340568716c18a8700ba3c0d3" 60 + 61 + [[package]] 62 + name = "pkg-config" 63 + version = "0.3.30" 64 + source = "registry+https://github.com/rust-lang/crates.io-index" 65 + checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" 66 + 67 + [[package]] 68 + name = "shlex" 69 + version = "1.3.0" 70 + source = "registry+https://github.com/rust-lang/crates.io-index" 71 + checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 72 + 73 + [[package]] 74 + name = "vcpkg" 75 + version = "0.2.15" 76 + source = "registry+https://github.com/rust-lang/crates.io-index" 77 + checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
+17
contrib/libgit-rs/Cargo.toml
··· 1 + [package] 2 + name = "libgit" 3 + version = "0.1.0" 4 + edition = "2021" 5 + build = "build.rs" 6 + rust-version = "1.63" # TODO: Once we hit 1.84 or newer, we may want to remove Cargo.lock from 7 + # version control. See https://lore.kernel.org/git/Z47jgK-oMjFRSslr@tapette.crustytoothpaste.net/ 8 + 9 + 10 + [lib] 11 + path = "src/lib.rs" 12 + 13 + [dependencies] 14 + libgit-sys = { version = "0.1.0", path = "../libgit-sys" } 15 + 16 + [build-dependencies] 17 + autocfg = "1.4.0"
+13
contrib/libgit-rs/README.md
··· 1 + # libgit-rs 2 + 3 + Proof-of-concept Git bindings for Rust. 4 + 5 + ```toml 6 + [dependencies] 7 + libgit = "0.1.0" 8 + ``` 9 + 10 + ## Rust version requirements 11 + 12 + libgit-rs should support Rust versions at least as old as the version included 13 + in Debian stable (currently 1.63).
+4
contrib/libgit-rs/build.rs
··· 1 + pub fn main() { 2 + let ac = autocfg::new(); 3 + ac.emit_has_path("std::ffi::c_char"); 4 + }
+106
contrib/libgit-rs/src/config.rs
··· 1 + use std::ffi::{c_void, CStr, CString}; 2 + use std::path::Path; 3 + 4 + #[cfg(has_std__ffi__c_char)] 5 + use std::ffi::{c_char, c_int}; 6 + 7 + #[cfg(not(has_std__ffi__c_char))] 8 + #[allow(non_camel_case_types)] 9 + type c_char = i8; 10 + 11 + #[cfg(not(has_std__ffi__c_char))] 12 + #[allow(non_camel_case_types)] 13 + type c_int = i32; 14 + 15 + use libgit_sys::*; 16 + 17 + /// A ConfigSet is an in-memory cache for config-like files such as `.gitmodules` or `.gitconfig`. 18 + /// It does not support all config directives; notably, it will not process `include` or 19 + /// `includeIf` directives (but it will store them so that callers can choose whether and how to 20 + /// handle them). 21 + pub struct ConfigSet(*mut libgit_config_set); 22 + impl ConfigSet { 23 + /// Allocate a new ConfigSet 24 + pub fn new() -> Self { 25 + unsafe { ConfigSet(libgit_configset_alloc()) } 26 + } 27 + 28 + /// Load the given files into the ConfigSet; conflicting directives in later files will 29 + /// override those given in earlier files. 30 + pub fn add_files(&mut self, files: &[&Path]) { 31 + for file in files { 32 + let pstr = file.to_str().expect("Invalid UTF-8"); 33 + let rs = CString::new(pstr).expect("Couldn't convert to CString"); 34 + unsafe { 35 + libgit_configset_add_file(self.0, rs.as_ptr()); 36 + } 37 + } 38 + } 39 + 40 + /// Load the value for the given key and attempt to parse it as an i32. Dies with a fatal error 41 + /// if the value cannot be parsed. Returns None if the key is not present. 42 + pub fn get_int(&mut self, key: &str) -> Option<i32> { 43 + let key = CString::new(key).expect("Couldn't convert to CString"); 44 + let mut val: c_int = 0; 45 + unsafe { 46 + if libgit_configset_get_int(self.0, key.as_ptr(), &mut val as *mut c_int) != 0 { 47 + return None; 48 + } 49 + } 50 + 51 + Some(val.into()) 52 + } 53 + 54 + /// Clones the value for the given key. Dies with a fatal error if the value cannot be 55 + /// converted to a String. Returns None if the key is not present. 56 + pub fn get_string(&mut self, key: &str) -> Option<String> { 57 + let key = CString::new(key).expect("Couldn't convert key to CString"); 58 + let mut val: *mut c_char = std::ptr::null_mut(); 59 + unsafe { 60 + if libgit_configset_get_string(self.0, key.as_ptr(), &mut val as *mut *mut c_char) != 0 61 + { 62 + return None; 63 + } 64 + let borrowed_str = CStr::from_ptr(val); 65 + let owned_str = 66 + String::from(borrowed_str.to_str().expect("Couldn't convert val to str")); 67 + free(val as *mut c_void); // Free the xstrdup()ed pointer from the C side 68 + Some(owned_str) 69 + } 70 + } 71 + } 72 + 73 + impl Default for ConfigSet { 74 + fn default() -> Self { 75 + Self::new() 76 + } 77 + } 78 + 79 + impl Drop for ConfigSet { 80 + fn drop(&mut self) { 81 + unsafe { 82 + libgit_configset_free(self.0); 83 + } 84 + } 85 + } 86 + 87 + #[cfg(test)] 88 + mod tests { 89 + use super::*; 90 + 91 + #[test] 92 + fn load_configs_via_configset() { 93 + let mut cs = ConfigSet::new(); 94 + cs.add_files(&[ 95 + Path::new("testdata/config1"), 96 + Path::new("testdata/config2"), 97 + Path::new("testdata/config3"), 98 + ]); 99 + // ConfigSet retrieves correct value 100 + assert_eq!(cs.get_int("trace2.eventTarget"), Some(1)); 101 + // ConfigSet respects last config value set 102 + assert_eq!(cs.get_int("trace2.eventNesting"), Some(3)); 103 + // ConfigSet returns None for missing key 104 + assert_eq!(cs.get_string("foo.bar"), None); 105 + } 106 + }
+1
contrib/libgit-rs/src/lib.rs
··· 1 + pub mod config;
+2
contrib/libgit-rs/testdata/config1
··· 1 + [trace2] 2 + eventNesting = 1
+2
contrib/libgit-rs/testdata/config2
··· 1 + [trace2] 2 + eventTarget = 1
+2
contrib/libgit-rs/testdata/config3
··· 1 + [trace2] 2 + eventNesting = 3
+4
contrib/libgit-sys/src/lib.rs
··· 1 + use std::ffi::c_void; 2 + 1 3 #[cfg(has_std__ffi__c_char)] 2 4 use std::ffi::{c_char, c_int}; 3 5 ··· 19 21 } 20 22 21 23 extern "C" { 24 + pub fn free(ptr: *mut c_void); 25 + 22 26 pub fn libgit_user_agent() -> *const c_char; 23 27 pub fn libgit_user_agent_sanitized() -> *const c_char; 24 28
+7 -2
t/Makefile
··· 178 178 .PHONY: pre-clean $(T) aggregate-results clean valgrind perf \ 179 179 check-chainlint clean-chainlint test-chainlint $(UNIT_TESTS) 180 180 181 - .PHONY: libgit-sys-test 181 + .PHONY: libgit-sys-test libgit-rs-test 182 182 libgit-sys-test: 183 183 $(QUIET)(\ 184 184 cd ../contrib/libgit-sys && \ 185 185 cargo test \ 186 186 ) 187 + libgit-rs-test: 188 + $(QUIET)(\ 189 + cd ../contrib/libgit-rs && \ 190 + cargo test \ 191 + ) 187 192 ifdef INCLUDE_LIBGIT_RS 188 - all:: libgit-sys-test 193 + all:: libgit-sys-test libgit-rs-test 189 194 endif