diff mbox series

[RFC,6/6] contrib/cgit-rs: add a subset of configset wrappers

Message ID 1e981a68802ac5aa7538381eb9469e524265ee40.1723054623.git.steadmon@google.com (mailing list archive)
State Superseded
Headers show
Series Introduce cgit-rs, a Rust wrapper around libgit.a | expand

Commit Message

Josh Steadmon Aug. 7, 2024, 6:21 p.m. UTC
From: Calvin Wan <calvinwan@google.com>

Signed-off-by: Calvin Wan <calvinwan@google.com>
Signed-off-by: Josh Steadmon <steadmon@google.com>
---
 contrib/cgit-rs/Cargo.lock             | 83 ++++++++++++++++++++++++++
 contrib/cgit-rs/Cargo.toml             |  1 +
 contrib/cgit-rs/public_symbol_export.c | 25 ++++++++
 contrib/cgit-rs/public_symbol_export.h | 10 ++++
 contrib/cgit-rs/src/lib.rs             | 65 +++++++++++++++++++-
 contrib/cgit-rs/src/main.rs            | 13 ++++
 6 files changed, 196 insertions(+), 1 deletion(-)

Comments

brian m. carlson Aug. 7, 2024, 9:40 p.m. UTC | #1
On 2024-08-07 at 18:21:31, Josh Steadmon wrote:
> diff --git a/contrib/cgit-rs/Cargo.toml b/contrib/cgit-rs/Cargo.toml
> index 7b55e6f3e1..5768fce9e5 100644
> --- a/contrib/cgit-rs/Cargo.toml
> +++ b/contrib/cgit-rs/Cargo.toml
> @@ -14,3 +14,4 @@ path = "src/lib.rs"
>  
>  [dependencies]
>  libc = "0.2.155"
> +home = "0.5.9"

Okay, here's where we get to my previous mention of supported platforms.
This depends on Rust 1.70, and Debian stable has only 1.63.  Trying
`cargo build --release` on that version returns this:

  Downloaded home v0.5.9
  Downloaded libc v0.2.155
  Downloaded 2 crates (752.3 KB) in 0.17s
error: package `home v0.5.9` cannot be built because it requires rustc 1.70.0 or newer, while the currently active rustc version is 1.63.0

My recommended approach here is to support the version in Debian stable,
plus the version in Debian oldstable for a year after the new stable
comes out, which is what I do.  That gives people a year to upgrade if
they want to use our code.  We _don't_ want to follow the
latest-stable-Rust approach because it isn't appropriate that software
has a six-week lifespan of support and that isn't going to work for
software like Git that people often compile locally on older versions.

We also need to be conscious that while Rust upstream provides some
binaries for some platforms, many platforms rely on the distro packages
because Rust upstream doesn't ship binaries for their target.  Thus,
simply using rustup is not viable for many targets, which is another
reason that latest-stable-Rust won't fly.

Debian stable is the version that most projects who have defined
lifespans track, so it's also what we should track.  According to my
recommended approach, that would be 1.63.

If the Rust project agrees to provide LTS versions, then we can switch
to those.

In any event, whatever we decide is necessarily going to involve us very
carefully planning our dependencies since some crates depend on the
latest version whenever it comes out and we're not going to want to do
that.

I'd also note that we don't actually want the home crate.  The
README says this:

  The definition of home_dir provided by the standard library is
  incorrect because it considers the HOME environment variable on
  Windows. This causes surprising situations where a Rust program will
  behave differently depending on whether it is run under a Unix
  emulation environment like Cygwin or MinGW. Neither Cargo nor rustup
  use the standard library's definition - they use the definition here.

Except that in Git, we _do_ want to honour `HOME` at all times.  Thus,
the use of the `home` crate instead of the standard library provides
exactly the wrong behaviour, and we should remove this dependency.
Junio C Hamano Aug. 7, 2024, 9:53 p.m. UTC | #2
"brian m. carlson" <sandals@crustytoothpaste.net> writes:

> My recommended approach here is to support the version in Debian stable,
> plus the version in Debian oldstable for a year after the new stable
> comes out, which is what I do.  That gives people a year to upgrade if
> they want to use our code.  We _don't_ want to follow the
> latest-stable-Rust approach because it isn't appropriate that software
> has a six-week lifespan of support and that isn't going to work for
> software like Git that people often compile locally on older versions.

This is sensible.  Conservative enough to avoid leaving folks on
other major distros behind, but still not too old to be a burden.
Josh Steadmon Aug. 8, 2024, 9:44 p.m. UTC | #3
On 2024.08.07 21:40, brian m. carlson wrote:
> On 2024-08-07 at 18:21:31, Josh Steadmon wrote:
> > diff --git a/contrib/cgit-rs/Cargo.toml b/contrib/cgit-rs/Cargo.toml
> > index 7b55e6f3e1..5768fce9e5 100644
> > --- a/contrib/cgit-rs/Cargo.toml
> > +++ b/contrib/cgit-rs/Cargo.toml
> > @@ -14,3 +14,4 @@ path = "src/lib.rs"
> >  
> >  [dependencies]
> >  libc = "0.2.155"
> > +home = "0.5.9"
> 
> Okay, here's where we get to my previous mention of supported platforms.
> This depends on Rust 1.70, and Debian stable has only 1.63.  Trying
> `cargo build --release` on that version returns this:
> 
>   Downloaded home v0.5.9
>   Downloaded libc v0.2.155
>   Downloaded 2 crates (752.3 KB) in 0.17s
> error: package `home v0.5.9` cannot be built because it requires rustc 1.70.0 or newer, while the currently active rustc version is 1.63.0
> 
> My recommended approach here is to support the version in Debian stable,
> plus the version in Debian oldstable for a year after the new stable
> comes out, which is what I do.  That gives people a year to upgrade if
> they want to use our code.  We _don't_ want to follow the
> latest-stable-Rust approach because it isn't appropriate that software
> has a six-week lifespan of support and that isn't going to work for
> software like Git that people often compile locally on older versions.
> 
> We also need to be conscious that while Rust upstream provides some
> binaries for some platforms, many platforms rely on the distro packages
> because Rust upstream doesn't ship binaries for their target.  Thus,
> simply using rustup is not viable for many targets, which is another
> reason that latest-stable-Rust won't fly.
> 
> Debian stable is the version that most projects who have defined
> lifespans track, so it's also what we should track.  According to my
> recommended approach, that would be 1.63.
> 
> If the Rust project agrees to provide LTS versions, then we can switch
> to those.
> 
> In any event, whatever we decide is necessarily going to involve us very
> carefully planning our dependencies since some crates depend on the
> latest version whenever it comes out and we're not going to want to do
> that.
> 
> I'd also note that we don't actually want the home crate.  The
> README says this:
> 
>   The definition of home_dir provided by the standard library is
>   incorrect because it considers the HOME environment variable on
>   Windows. This causes surprising situations where a Rust program will
>   behave differently depending on whether it is run under a Unix
>   emulation environment like Cygwin or MinGW. Neither Cargo nor rustup
>   use the standard library's definition - they use the definition here.
> 
> Except that in Git, we _do_ want to honour `HOME` at all times.  Thus,
> the use of the `home` crate instead of the standard library provides
> exactly the wrong behaviour, and we should remove this dependency.
> -- 
> brian m. carlson (they/them or he/him)
> Toronto, Ontario, CA

I've replaced home::home_dir with std::env::home_dir, however the latter
is deprecated, so we may want to reimplement this entirely.
Calvin Wan Sept. 4, 2024, 5:30 p.m. UTC | #4
"brian m. carlson" <sandals@crustytoothpaste.net> writes:
> On 2024-08-07 at 18:21:31, Josh Steadmon wrote:
> > diff --git a/contrib/cgit-rs/Cargo.toml b/contrib/cgit-rs/Cargo.toml
> > index 7b55e6f3e1..5768fce9e5 100644
> > --- a/contrib/cgit-rs/Cargo.toml
> > +++ b/contrib/cgit-rs/Cargo.toml
> > @@ -14,3 +14,4 @@ path = "src/lib.rs"
> >  
> >  [dependencies]
> >  libc = "0.2.155"
> > +home = "0.5.9"
> 
> Okay, here's where we get to my previous mention of supported platforms.
> This depends on Rust 1.70, and Debian stable has only 1.63.  Trying
> `cargo build --release` on that version returns this:
> 
>   Downloaded home v0.5.9
>   Downloaded libc v0.2.155
>   Downloaded 2 crates (752.3 KB) in 0.17s
> error: package `home v0.5.9` cannot be built because it requires rustc 1.70.0 or newer, while the currently active rustc version is 1.63.0
> 
> My recommended approach here is to support the version in Debian stable,
> plus the version in Debian oldstable for a year after the new stable
> comes out, which is what I do.  That gives people a year to upgrade if
> they want to use our code.  We _don't_ want to follow the
> latest-stable-Rust approach because it isn't appropriate that software
> has a six-week lifespan of support and that isn't going to work for
> software like Git that people often compile locally on older versions.
> 
> We also need to be conscious that while Rust upstream provides some
> binaries for some platforms, many platforms rely on the distro packages
> because Rust upstream doesn't ship binaries for their target.  Thus,
> simply using rustup is not viable for many targets, which is another
> reason that latest-stable-Rust won't fly.
> 
> Debian stable is the version that most projects who have defined
> lifespans track, so it's also what we should track.  According to my
> recommended approach, that would be 1.63.

After getting rid of the `home` crate, the only other issue we ran into
downgrading the version to 1.63 was with `std::ffi{c_char, c_int}`.
Those were only made available in 1.64 and they are obviously quite
necessary for us to be able to call C functions. Do you know of any
alternatives we can use? I also don't think reinventing the wheel with
our own implementation makes sense in this case, and even if Debian were
to upgrade stable to a higher version today, we would still need to
support oldstable for another year.
brian m. carlson Sept. 4, 2024, 5:49 p.m. UTC | #5
On 2024-09-04 at 17:30:53, Calvin Wan wrote:
> After getting rid of the `home` crate, the only other issue we ran into
> downgrading the version to 1.63 was with `std::ffi{c_char, c_int}`.
> Those were only made available in 1.64 and they are obviously quite
> necessary for us to be able to call C functions. Do you know of any
> alternatives we can use? I also don't think reinventing the wheel with
> our own implementation makes sense in this case, and even if Debian were
> to upgrade stable to a higher version today, we would still need to
> support oldstable for another year.

I think we can do this with libc, which you're importing at the moment.
You can do something like this:

src/types.rs:
----
pub use libc::{c_char, c_int};
----

and then do `use crate::types::{c_char, c_int}` wherever you want them.

Then, when we upgrade to 1.64 or newer, we can simply replace the
contents of `src/types.rs` with this:

----
pub use std::ffi::{c_char, c_int};
----

and that's the only thing we'll need to change.

If we switch to using rustix instead, then it will look like this:

----
pub use rustix::ffi::c_char;

pub type c_int = i32;
----

While the C standard requires that `int` only has 16 bits, Git doesn't
target any platforms where `int` is anything but 32 bits and it won't
work with any other configuration without serious changes.
Junio C Hamano Sept. 4, 2024, 6:33 p.m. UTC | #6
Calvin Wan <calvinwan@google.com> writes:

> "brian m. carlson" <sandals@crustytoothpaste.net> writes:
>> ...
>> Debian stable is the version that most projects who have defined
>> lifespans track, so it's also what we should track.  According to my
>> recommended approach, that would be 1.63.
>
> ... I also don't think reinventing the wheel with
> our own implementation makes sense in this case,

I do agree that we would want to avoid that.

> and even if Debian were
> to upgrade stable to a higher version today, we would still need to
> support oldstable for another year.

I doubt that part, though.  As long as the rust binding stays an
optional code, as long as we are supported by the "current" system,
we would still have enough audience to matter.

What's the primary objective of this effort, by the way?  

Is it "we need to access the guts of Git implementation from Rust"?
Or does it merely serve as an example application to have Rust
bindings, a good goal to have to give us an incentive to clean up
the subsystem interactions in our code?  

If it is the former, we cannot reasonably achieve that goal until
some form of standardized foreign function interface becomes
available to wide audience.  If it is the latter, on the other hand,
it does not have to be Rust---if the version of Rust that is
distirbuted to the mainstream users is not yet ready to be used in
such a way, we could pick another goal (like, "Can we clean-up the
interface cgit uses to call into us, so that the parts used by them
look more like a proper library?").

Thanks.
brian m. carlson Sept. 4, 2024, 7:03 p.m. UTC | #7
On 2024-09-04 at 18:33:17, Junio C Hamano wrote:
> Is it "we need to access the guts of Git implementation from Rust"?
> Or does it merely serve as an example application to have Rust
> bindings, a good goal to have to give us an incentive to clean up
> the subsystem interactions in our code?
> 
> If it is the former, we cannot reasonably achieve that goal until
> some form of standardized foreign function interface becomes
> available to wide audience.  If it is the latter, on the other hand,
> it does not have to be Rust---if the version of Rust that is
> distirbuted to the mainstream users is not yet ready to be used in
> such a way, we could pick another goal (like, "Can we clean-up the
> interface cgit uses to call into us, so that the parts used by them
> look more like a proper library?").

Traditionally, in Rust, you don't use the C-style types because that
leads to portability problems.  Look at how using "unsigned long" as
"equivalent in size to void *" has gotten our C code to have sharp edges
on Windows, where that isn't true. The approach one typically uses is to
use things like int32_t, which is i32 in Rust, and size_t, which is
usize in Rust.  This leads to much more predictable behaviour on both
sides of the FFI.

The C-style types have long been available in libc and other crates that
are designed to work with C FFI, and as a practical matter you do need
to use that crate somewhere in your stack (or reimplement it) to call
functions in libc and the other core system libraries, so you're not
really lacking those types.

They're also available in the `std::os::raw` module as of Rust 1.1; it's
just that as of Rust 1.64, they're in `std::ffi` and `core::ffi` as
well, mostly to help embedded systems (which don't have `std`, and thus,
don't have `std::os::raw`).  Using `std::os::raw` or `libc` should be
fine for Git, since we're not targeting operating system kernels,
bootloaders, or firmware such as UEFI (I hope).
Junio C Hamano Sept. 4, 2024, 9:08 p.m. UTC | #8
"brian m. carlson" <sandals@crustytoothpaste.net> writes:

> The C-style types have long been available in libc and other crates that
> are designed to work with C FFI, and as a practical matter you do need
> to use that crate somewhere in your stack (or reimplement it) to call
> functions in libc and the other core system libraries, so you're not
> really lacking those types.

Glad to know that the feature we need was already there.  So, it is
just the interface was in flux when they worked on the version
Debian happens to ship right now, I guess.

> They're also available in the `std::os::raw` module as of Rust 1.1; it's
> just that as of Rust 1.64, they're in `std::ffi` and `core::ffi` as
> well, mostly to help embedded systems (which don't have `std`, and thus,
> don't have `std::os::raw`).  Using `std::os::raw` or `libc` should be
> fine for Git, since we're not targeting operating system kernels,
> bootloaders, or firmware such as UEFI (I hope).

Yup, as long as we have a clean migration path (like the one you
showed Calvin in your other message in this thread), and use of a
slightly older way will not immediately be deprecated in a newer
version of Rust, then we should be fine going forward.

Thanks.
Josh Steadmon Sept. 4, 2024, 9:29 p.m. UTC | #9
On 2024.09.04 11:33, Junio C Hamano wrote:
> Calvin Wan <calvinwan@google.com> writes:
> 
> > "brian m. carlson" <sandals@crustytoothpaste.net> writes:
> >> ...
> >> Debian stable is the version that most projects who have defined
> >> lifespans track, so it's also what we should track.  According to my
> >> recommended approach, that would be 1.63.
> >
> > ... I also don't think reinventing the wheel with
> > our own implementation makes sense in this case,
> 
> I do agree that we would want to avoid that.
> 
> > and even if Debian were
> > to upgrade stable to a higher version today, we would still need to
> > support oldstable for another year.
> 
> I doubt that part, though.  As long as the rust binding stays an
> optional code, as long as we are supported by the "current" system,
> we would still have enough audience to matter.
> 
> What's the primary objective of this effort, by the way?  
> 
> Is it "we need to access the guts of Git implementation from Rust"?
> Or does it merely serve as an example application to have Rust
> bindings, a good goal to have to give us an incentive to clean up
> the subsystem interactions in our code?  

For us at $DAYJOB, it's "we need to access the guts of Git from Rust".

> If it is the former, we cannot reasonably achieve that goal until
> some form of standardized foreign function interface becomes
> available to wide audience.  If it is the latter, on the other hand,
> it does not have to be Rust---if the version of Rust that is
> distirbuted to the mainstream users is not yet ready to be used in
> such a way, we could pick another goal (like, "Can we clean-up the
> interface cgit uses to call into us, so that the parts used by them
> look more like a proper library?").
> 
> Thanks.

I think brian already addressed your point about having a standard FFI,
but if there's anything that still needs clarification please let me
know.
Calvin Wan Sept. 6, 2024, 7:37 p.m. UTC | #10
On Wed, Sep 4, 2024 at 10:49 AM brian m. carlson
<sandals@crustytoothpaste.net> wrote:
>
> On 2024-09-04 at 17:30:53, Calvin Wan wrote:
> > After getting rid of the `home` crate, the only other issue we ran into
> > downgrading the version to 1.63 was with `std::ffi{c_char, c_int}`.
> > Those were only made available in 1.64 and they are obviously quite
> > necessary for us to be able to call C functions. Do you know of any
> > alternatives we can use? I also don't think reinventing the wheel with
> > our own implementation makes sense in this case, and even if Debian were
> > to upgrade stable to a higher version today, we would still need to
> > support oldstable for another year.
>
> I think we can do this with libc, which you're importing at the moment.
> You can do something like this:
>
> src/types.rs:
> ----
> pub use libc::{c_char, c_int};
> ----
>
> and then do `use crate::types::{c_char, c_int}` wherever you want them.

While this gets rid of `std::ffi{c_char, c_int}` dependency,
`std::ffi{CStr, CString}` are also 1.64 unfortunately, and I don't see
the equivalent of it in libc. I'll reroll v3 without this change for
now. Hopefully Debian stable just upgrades to 1.64 or higher and we
don't have to worry about these ffi conversions :)
brian m. carlson Sept. 7, 2024, 2:53 p.m. UTC | #11
On 2024-09-06 at 19:37:18, Calvin Wan wrote:
> While this gets rid of `std::ffi{c_char, c_int}` dependency,
> `std::ffi{CStr, CString}` are also 1.64 unfortunately, and I don't see
> the equivalent of it in libc. I'll reroll v3 without this change for
> now. Hopefully Debian stable just upgrades to 1.64 or higher and we
> don't have to worry about these ffi conversions :)

I think that's actually a mistake, since some arguments like
`from_bytes_with_nul` are labeled as 1.10.0.  In any event, the
following program compiles fine for me on 1.63, so I'm not worried about
their usage:

----
use std::ffi::CStr;

fn main() {
    let c = CStr::from_bytes_with_nul(b"abc\0");
    let c = c.to_owned();
    println!("Hello, world!\n");
}
----
diff mbox series

Patch

diff --git a/contrib/cgit-rs/Cargo.lock b/contrib/cgit-rs/Cargo.lock
index f50c593995..1d40ac5faa 100644
--- a/contrib/cgit-rs/Cargo.lock
+++ b/contrib/cgit-rs/Cargo.lock
@@ -6,11 +6,94 @@  version = 3
 name = "cgit"
 version = "0.1.0"
 dependencies = [
+ "home",
  "libc",
 ]
 
+[[package]]
+name = "home"
+version = "0.5.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5"
+dependencies = [
+ "windows-sys",
+]
+
 [[package]]
 name = "libc"
 version = "0.2.155"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c"
+
+[[package]]
+name = "windows-sys"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
+dependencies = [
+ "windows-targets",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
+dependencies = [
+ "windows_aarch64_gnullvm",
+ "windows_aarch64_msvc",
+ "windows_i686_gnu",
+ "windows_i686_gnullvm",
+ "windows_i686_msvc",
+ "windows_x86_64_gnu",
+ "windows_x86_64_gnullvm",
+ "windows_x86_64_msvc",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
+
+[[package]]
+name = "windows_i686_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
diff --git a/contrib/cgit-rs/Cargo.toml b/contrib/cgit-rs/Cargo.toml
index 7b55e6f3e1..5768fce9e5 100644
--- a/contrib/cgit-rs/Cargo.toml
+++ b/contrib/cgit-rs/Cargo.toml
@@ -14,3 +14,4 @@  path = "src/lib.rs"
 
 [dependencies]
 libc = "0.2.155"
+home = "0.5.9"
diff --git a/contrib/cgit-rs/public_symbol_export.c b/contrib/cgit-rs/public_symbol_export.c
index ab3401ac40..9641afca89 100644
--- a/contrib/cgit-rs/public_symbol_export.c
+++ b/contrib/cgit-rs/public_symbol_export.c
@@ -31,6 +31,31 @@  int libgit_parse_maybe_bool(const char *val)
 	return git_parse_maybe_bool(val);
 }
 
+struct config_set *libgit_configset_alloc(void)
+{
+	return git_configset_alloc();
+}
+
+void libgit_configset_init(struct config_set *cs)
+{
+	git_configset_init(cs);
+}
+
+int libgit_configset_add_file(struct config_set *cs, const char *filename)
+{
+	return git_configset_add_file(cs, filename);
+}
+
+int libgit_configset_get_int(struct config_set *cs, const char *key, int *dest)
+{
+	return git_configset_get_int(cs, key, dest);
+}
+
+int libgit_configset_get_string(struct config_set *cs, const char *key, char **dest)
+{
+	return git_configset_get_string(cs, key, dest);
+}
+
 const char *libgit_user_agent(void)
 {
 	return git_user_agent();
diff --git a/contrib/cgit-rs/public_symbol_export.h b/contrib/cgit-rs/public_symbol_export.h
index 97e8e871ba..f22937202a 100644
--- a/contrib/cgit-rs/public_symbol_export.h
+++ b/contrib/cgit-rs/public_symbol_export.h
@@ -9,6 +9,16 @@  void libgit_initialize_the_repository(void);
 
 int libgit_parse_maybe_bool(const char *val);
 
+struct config_set *libgit_configset_alloc(void);
+
+void libgit_configset_init(struct config_set *cs);
+
+int libgit_configset_add_file(struct config_set *cs, const char *filename);
+
+int libgit_configset_get_int(struct config_set *cs, const char *key, int *dest);
+
+int libgit_configset_get_string(struct config_set *cs, const char *key, char **dest);
+
 const char *libgit_user_agent(void);
 
 const char *libgit_user_agent_sanitized(void);
diff --git a/contrib/cgit-rs/src/lib.rs b/contrib/cgit-rs/src/lib.rs
index df350e758f..acfd3659e8 100644
--- a/contrib/cgit-rs/src/lib.rs
+++ b/contrib/cgit-rs/src/lib.rs
@@ -1,4 +1,57 @@ 
-use libc::{c_char, c_int};
+use std::ffi::{CStr, CString};
+
+use libc::{c_char, c_int, c_void};
+
+pub enum GitConfigSet {}
+
+pub struct ConfigSet(*mut GitConfigSet);
+impl ConfigSet {
+
+    pub fn new() -> Self {
+        unsafe {
+            // TODO: we need to handle freeing this when the ConfigSet is dropped
+            // git_configset_clear(ptr) and then libc::free(ptr)
+            let ptr = libgit_configset_alloc();
+            libgit_configset_init(ptr);
+            ConfigSet(ptr)
+        }
+    }
+
+    pub fn add_files(&mut self, files: &[&str]) {
+        for file in files {
+            let rs = CString::new(*file).expect("Couldn't convert to CString");
+            unsafe {
+                libgit_configset_add_file(self.0, rs.as_ptr());
+            }
+        }
+    }
+
+    pub fn get_int(&mut self, key: &str) -> Option<c_int> {
+        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)
+    }
+
+    pub fn get_str(&mut self, key: &str) -> Option<CString> {
+        let key = CString::new(key).expect("Couldn't convert 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 = CString::from_vec_with_nul(borrowed_str.to_bytes_with_nul().to_vec());
+            libc::free(val as *mut c_void); // Free the xstrdup()ed pointer from the C side
+            Some(owned_str.unwrap())
+        }
+    }
+}
 
 extern "C" {
     pub fn libgit_setup_git_directory() -> *const c_char;
@@ -15,4 +68,14 @@  extern "C" {
     // From version.c
     pub fn libgit_user_agent() -> *const c_char;
     pub fn libgit_user_agent_sanitized() -> *const c_char;
+
+    fn libgit_configset_alloc() -> *mut GitConfigSet;
+
+    fn libgit_configset_init(cs: *mut GitConfigSet);
+
+    fn libgit_configset_add_file(cs: *mut GitConfigSet, filename: *const c_char) -> c_int;
+
+    pub fn libgit_configset_get_int(cs: *mut GitConfigSet, key: *const c_char, int: *mut c_int) -> c_int;
+    pub fn libgit_configset_get_string(cs: *mut GitConfigSet, key: *const c_char, dest: *mut *mut c_char) -> c_int;
+
 }
diff --git a/contrib/cgit-rs/src/main.rs b/contrib/cgit-rs/src/main.rs
index c5f8644fca..07c8a71a13 100644
--- a/contrib/cgit-rs/src/main.rs
+++ b/contrib/cgit-rs/src/main.rs
@@ -1,4 +1,5 @@ 
 use std::ffi::{CStr, CString};
+use home::home_dir;
 
 fn main() {
     println!("Let's print some strings provided by Git");
@@ -28,4 +29,16 @@  fn main() {
             val
         );
     };
+
+    println!("\nTry out our configset wrappers");
+    let mut cs = cgit::ConfigSet::new();
+    let mut path = home_dir().expect("cannot get home directory path");
+    path.push(".gitconfig");
+    let path:String = path.into_os_string().into_string().unwrap();
+    cs.add_files(&["/etc/gitconfig", ".gitconfig", &path]);
+    /*
+     * Returns Some(x) if defined in local config, otherwise None
+     */
+    println!("get_configset_get_int = {:?}", cs.get_int("trace2.eventNesting"));
+    println!("cs.get_str(\"garbage\") = {:?}", cs.get_str("this_string_does_not_exist"));
 }