From 0a145bc6bd4322410864402e2ab8cae305bef07d Mon Sep 17 00:00:00 2001 From: Daniel Cazzulino Date: Tue, 21 Jul 2026 23:23:34 -0300 Subject: [PATCH] Fix config discovery under Native AOT (GitBuf marshalling) Under Native AOT, sequential-layout class marshalling for GitBuf is effectively [In]-only, so git_config_find_* succeeds but managed ptr stays zero and global/system config never loads. BuildSignature then returns null when user.name/email live outside local repo config. Use a blittable GitBufNative struct with ref P/Invoke for path-find APIs and ConvertPath so config discovery works in AOT publishes. Fixes #2187 --- LibGit2Sharp/Core/GitBuf.cs | 23 +++++++++++++++++++++++ LibGit2Sharp/Core/NativeMethods.cs | 15 ++++++++++----- LibGit2Sharp/Core/Proxy.cs | 20 ++++++++++++++++---- 3 files changed, 49 insertions(+), 9 deletions(-) diff --git a/LibGit2Sharp/Core/GitBuf.cs b/LibGit2Sharp/Core/GitBuf.cs index 19b1328b9..c19fd8487 100644 --- a/LibGit2Sharp/Core/GitBuf.cs +++ b/LibGit2Sharp/Core/GitBuf.cs @@ -3,6 +3,16 @@ namespace LibGit2Sharp.Core.Handles { + /// + /// Managed mirror of libgit2's git_buf for APIs that still pass a + /// sequential-layout class through the runtime marshaller. + /// + /// + /// Prefer for new or Native AOT-sensitive call + /// sites. Under Native AOT, class-based sequential marshalling is effectively + /// [In]-only, so native writes to ptr/size never appear in + /// managed code. + /// [StructLayout(LayoutKind.Sequential)] internal class GitBuf : IDisposable { @@ -15,4 +25,17 @@ public void Dispose() Proxy.git_buf_dispose(this); } } + + /// + /// Blittable git_buf for P/Invoke via ref. Required for Native AOT + /// so that libgit2 can write ptr/asize/size back to managed + /// memory (class marshalling does not). + /// + [StructLayout(LayoutKind.Sequential)] + internal struct GitBufNative + { + public IntPtr ptr; + public UIntPtr asize; + public UIntPtr size; + } } diff --git a/LibGit2Sharp/Core/NativeMethods.cs b/LibGit2Sharp/Core/NativeMethods.cs index cbb850b16..be3f62540 100644 --- a/LibGit2Sharp/Core/NativeMethods.cs +++ b/LibGit2Sharp/Core/NativeMethods.cs @@ -360,6 +360,9 @@ internal static extern unsafe int git_branch_upstream_name( [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] internal static extern void git_buf_dispose(GitBuf buf); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern void git_buf_dispose(ref GitBufNative buf); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] internal static extern unsafe int git_checkout_tree( git_repository* repo, @@ -468,17 +471,19 @@ internal static extern unsafe int git_config_set_multivar( [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string regexp, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string value); + // Use ref GitBufNative (not class GitBuf): under Native AOT, sequential + // class marshalling does not write ptr/size back to the managed object. [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] - internal static extern int git_config_find_global(GitBuf global_config_path); + internal static extern int git_config_find_global(ref GitBufNative global_config_path); [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] - internal static extern int git_config_find_system(GitBuf system_config_path); + internal static extern int git_config_find_system(ref GitBufNative system_config_path); [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] - internal static extern int git_config_find_xdg(GitBuf xdg_config_path); + internal static extern int git_config_find_xdg(ref GitBufNative xdg_config_path); [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] - internal static extern int git_config_find_programdata(GitBuf programdata_config_path); + internal static extern int git_config_find_programdata(ref GitBufNative programdata_config_path); [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] internal static extern unsafe void git_config_free(git_config* cfg); @@ -1522,7 +1527,7 @@ IntPtr data [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] internal static extern int git_repository_discover( - GitBuf buf, + ref GitBufNative buf, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictFilePathMarshaler))] FilePath start_path, [MarshalAs(UnmanagedType.Bool)] bool across_fs, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictFilePathMarshaler))] FilePath ceiling_dirs); diff --git a/LibGit2Sharp/Core/Proxy.cs b/LibGit2Sharp/Core/Proxy.cs index 83d35e22c..95c7b3325 100644 --- a/LibGit2Sharp/Core/Proxy.cs +++ b/LibGit2Sharp/Core/Proxy.cs @@ -2447,7 +2447,7 @@ public static unsafe string git_remote_pushurl(RemoteHandle remote) public static FilePath git_repository_discover(FilePath start_path) { - return ConvertPath(buf => NativeMethods.git_repository_discover(buf, start_path, false, null)); + return ConvertPath((ref GitBufNative buf) => NativeMethods.git_repository_discover(ref buf, start_path, false, null)); } public static unsafe bool git_repository_head_detached(RepositoryHandle repo) @@ -3830,11 +3830,19 @@ private static unsafe bool RepositoryStateChecker(RepositoryHandle repo, Func pathRetriever) + private delegate int PathRetriever(ref GitBufNative buf); + + /// + /// Invokes a native path-returning API using a blittable . + /// Must not use class : Native AOT marshalling does not + /// copy native fills of ptr/size back onto sequential-layout classes. + /// + private static FilePath ConvertPath(PathRetriever pathRetriever) { - using (var buf = new GitBuf()) + var buf = new GitBufNative(); + try { - int result = pathRetriever(buf); + int result = pathRetriever(ref buf); if (result == (int)GitErrorCode.NotFound) { @@ -3844,6 +3852,10 @@ private static FilePath ConvertPath(Func pathRetriever) Ensure.ZeroResult(result); return LaxFilePathMarshaler.FromNative(buf.ptr); } + finally + { + NativeMethods.git_buf_dispose(ref buf); + } } private static readonly IDictionary> configurationParser = new Dictionary>