Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions LibGit2Sharp.Tests/LibGit2Sharp.Tests.csproj
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks Condition="'$(TargetFrameworks)'==''">net472;net8.0;net9.0</TargetFrameworks>
<TargetFrameworks Condition="'$(TargetFrameworks)'==''">net10.0</TargetFrameworks>
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bunch of changes to get it building locally

</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\LibGit2Sharp\LibGit2Sharp.csproj" />
<ProjectReference Include="..\NativeLibraryLoadTestApp\x86\NativeLibraryLoadTestApp.x86.csproj" Condition="'$(TargetFramework)' == 'net472'" ReferenceOutputAssembly="false" OutputItemType="TestAppExe" />
<ProjectReference Include="..\NativeLibraryLoadTestApp\x64\NativeLibraryLoadTestApp.x64.csproj" Condition="'$(TargetFramework)' == 'net472'" ReferenceOutputAssembly="false" OutputItemType="TestAppExe" />
<!--<ProjectReference Include="..\NativeLibraryLoadTestApp\x86\NativeLibraryLoadTestApp.x86.csproj" Condition="'$(TargetFramework)' == 'net472'" ReferenceOutputAssembly="false" OutputItemType="TestAppExe" />
<ProjectReference Include="..\NativeLibraryLoadTestApp\x64\NativeLibraryLoadTestApp.x64.csproj" Condition="'$(TargetFramework)' == 'net472'" ReferenceOutputAssembly="false" OutputItemType="TestAppExe" />-->
</ItemGroup>

<ItemGroup>
Expand Down
10 changes: 7 additions & 3 deletions LibGit2Sharp.Tests/NetworkFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ namespace LibGit2Sharp.Tests
public class NetworkFixture : BaseFixture
{
[Theory]
[InlineData("http://github.com/libgit2/TestGitRepository")]
[InlineData("https://github.com/libgit2/TestGitRepository")]
//[InlineData("http://github.com/libgit2/TestGitRepository")]
[InlineData("git@github.com:libgit2/TestGitRepository.git")]
public void CanListRemoteReferences(string url)
{
string remoteName = "testRemote";
Expand All @@ -20,7 +20,11 @@ public void CanListRemoteReferences(string url)
using (var repo = new Repository(repoPath))
{
Remote remote = repo.Network.Remotes.Add(remoteName, url);
IList<Reference> references = repo.Network.ListReferences(remote).ToList();
IList<Reference> references = repo.Network.ListReferences(remote, (s, fromUrl, types) =>
{

return null;
}).ToList();


foreach (var reference in references)
Expand Down
141 changes: 141 additions & 0 deletions LibGit2Sharp/Core/SshExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@

Copy link
Contributor Author

@zentron zentron Mar 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This methods are required to be able to call down into the c libraries methods that are only available when using libssh2. The files in this class can be properly tidied up and split up before merge

using LibGit2Sharp.Core;
using System;
using System.Runtime.InteropServices;

namespace LibGit2Sharp.Ssh
{

internal static class NativeMethods
{
private const string libgit2 = NativeDllName.Name;

[DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)]
internal static extern int git_cred_ssh_key_new(
out IntPtr cred,
[MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string username,
[MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string publickey,
[MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string privatekey,
[MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string passphrase);

[DllImport(libgit2)]
internal static extern int git_cred_ssh_key_memory_new(
out IntPtr cred,
[MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string username,
[MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string publickey,
[MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string privatekey,
[MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string passphrase);
}

/// <summary>
/// Class that holds SSH username with key credentials for remote repository access.
/// </summary>
public sealed class SshUserKeyCredentials : Credentials
{
/// <summary>
/// Callback to acquire a credential object.
/// </summary>
/// <param name="cred">The newly created credential object.</param>
/// <returns>0 for success, &lt; 0 to indicate an error, &gt; 0 to indicate no credential was acquired.</returns>
protected internal override int GitCredentialHandler(out IntPtr cred)
{
if (Username == null)
{
throw new InvalidOperationException("SshUserKeyCredentials contains a null Username.");
}

if (Passphrase == null)
{
throw new InvalidOperationException("SshUserKeyCredentials contains a null Passphrase.");
}

if (PublicKey == null)
{
throw new InvalidOperationException("SshUserKeyCredentials contains a null PublicKey.");
}

if (PrivateKey == null)
{
throw new InvalidOperationException("SshUserKeyCredentials contains a null PrivateKey.");
}

return NativeMethods.git_cred_ssh_key_new(out cred, Username, PublicKey, PrivateKey, Passphrase);
}

/// <summary>
/// Username for SSH authentication.
/// </summary>
public string Username { get; set; }

/// <summary>
/// Public key file location for SSH authentication.
/// </summary>
public string PublicKey { get; set; }

/// <summary>
/// Private key file location for SSH authentication.
/// </summary>
public string PrivateKey { get; set; }

/// <summary>
/// Passphrase for SSH authentication.
/// </summary>
public string Passphrase { get; set; }
}

/// <summary>
/// Class that holds SSH username with in-memory key credentials for remote repository access.
/// </summary>
public sealed class SshUserKeyMemoryCredentials : Credentials
{
/// <summary>
/// Callback to acquire a credential object.
/// </summary>
/// <param name="cred">The newly created credential object.</param>
/// <returns>0 for success, &lt; 0 to indicate an error, &gt; 0 to indicate no credential was acquired.</returns>
protected internal override int GitCredentialHandler(out IntPtr cred)
{
if (Username == null)
{
throw new InvalidOperationException("SshUserKeyMemoryCredentials contains a null Username.");
}

if (Passphrase == null)
{
throw new InvalidOperationException("SshUserKeyMemoryCredentials contains a null Passphrase.");
}

if (PublicKey == null)
{
//throw new InvalidOperationException("SshUserKeyMemoryCredentials contains a null PublicKey.");
}

if (PrivateKey == null)
{
throw new InvalidOperationException("SshUserKeyMemoryCredentials contains a null PrivateKey.");
}

return NativeMethods.git_cred_ssh_key_memory_new(out cred, Username, PublicKey, PrivateKey, Passphrase);
}

/// <summary>
/// Username for SSH authentication.
/// </summary>
public string Username { get; set; }

/// <summary>
/// Public key for SSH authentication.
/// </summary>
public string PublicKey { get; set; }

/// <summary>
/// Private key for SSH authentication.
/// </summary>
public string PrivateKey { get; set; }

/// <summary>
/// Passphrase for SSH authentication.
/// </summary>
public string Passphrase { get; set; }
}
}
2 changes: 1 addition & 1 deletion LibGit2Sharp/GlobalSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public static class GlobalSettings

private static string nativeLibraryPath;
private static bool nativeLibraryPathLocked;
private static readonly string nativeLibraryDefaultPath = null;
private static readonly string nativeLibraryDefaultPath = "/Users/robert/Development/Sandbox/libgit2/build";
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Obviously not something to merge, just that helped testing against my local build of libgit2


static GlobalSettings()
{
Expand Down
26 changes: 26 additions & 0 deletions LibGit2Sharp/ListRemoteOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using LibGit2Sharp.Handlers;

namespace LibGit2Sharp;

/// <summary>
/// Options controlling ListRemote behavior.
/// </summary>
public sealed class ListRemoteOptions
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New ListRemoteOptions required to get appropriate handlers on the network list operation

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can probably be put in as its own PR back into the upstream repo as its not really dependant on anything ssh

{
/// <summary>
/// Handler to generate <see cref="LibGit2Sharp.Credentials"/> for authentication.
/// </summary>
public CredentialsHandler CredentialsProvider { get; set; }

/// <summary>
/// This handler will be called to let the user make a decision on whether to allow
/// the connection to proceed based on the certificate presented by the server.
/// </summary>
public CertificateCheckHandler CertificateCheck { get; set; }


/// <summary>
/// Options for connecting through a proxy.
/// </summary>
public ProxyOptions ProxyOptions { get; set; } = new();
}
105 changes: 93 additions & 12 deletions LibGit2Sharp/Network.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,34 @@ public virtual IEnumerable<Reference> ListReferences(Remote remote)
{
Ensure.ArgumentNotNull(remote, "remote");

return ListReferencesInternal(remote.Url, null, new ProxyOptions());
var options = new ListRemoteOptions()
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are a bunch of overloads in this network class to utilize the new ListRemoteOptions, these dont necessarily need to be implemented exactly like this

{
ProxyOptions = new ProxyOptions()
};

return ListReferencesInternal(remote.Url, options);
}

/// <summary>
/// List references in a <see cref="Remote"/> repository.
/// <para>
/// When the remote tips are ahead of the local ones, the retrieved
/// <see cref="DirectReference"/>s may point to non existing
/// <see cref="GitObject"/>s in the local repository. In that
/// case, <see cref="DirectReference.Target"/> will return <c>null</c>.
/// </para>
/// </summary>
/// <param name="remote">The <see cref="Remote"/> to list from.</param>
/// <param name="options">The options for the remote request.</param>
/// <returns>The references in the <see cref="Remote"/> repository.</returns>
public virtual IEnumerable<Reference> ListReferences(Remote remote, ListRemoteOptions options)
{
Ensure.ArgumentNotNull(remote, "remote");

return ListReferencesInternal(remote.Url, options);
}


/// <summary>
/// List references in a <see cref="Remote"/> repository.
/// <para>
Expand All @@ -71,7 +96,12 @@ public virtual IEnumerable<Reference> ListReferences(Remote remote, ProxyOptions
{
Ensure.ArgumentNotNull(remote, "remote");

return ListReferencesInternal(remote.Url, null, proxyOptions);
var options = new ListRemoteOptions()
{
ProxyOptions = proxyOptions
};

return ListReferencesInternal(remote.Url, options);
}

/// <summary>
Expand All @@ -91,7 +121,13 @@ public virtual IEnumerable<Reference> ListReferences(Remote remote, CredentialsH
Ensure.ArgumentNotNull(remote, "remote");
Ensure.ArgumentNotNull(credentialsProvider, "credentialsProvider");

return ListReferencesInternal(remote.Url, credentialsProvider, new ProxyOptions());
var options = new ListRemoteOptions()
{
ProxyOptions = new ProxyOptions(),
CredentialsProvider = credentialsProvider
};

return ListReferencesInternal(remote.Url, options);
}

/// <summary>
Expand All @@ -112,7 +148,32 @@ public virtual IEnumerable<Reference> ListReferences(Remote remote, CredentialsH
Ensure.ArgumentNotNull(remote, "remote");
Ensure.ArgumentNotNull(credentialsProvider, "credentialsProvider");

return ListReferencesInternal(remote.Url, credentialsProvider, proxyOptions);
var options = new ListRemoteOptions()
{
ProxyOptions = proxyOptions,
CredentialsProvider = credentialsProvider
};

return ListReferencesInternal(remote.Url, options);
}

/// <summary>
/// List references in a remote repository.
/// <para>
/// When the remote tips are ahead of the local ones, the retrieved
/// <see cref="DirectReference"/>s may point to non existing
/// <see cref="GitObject"/>s in the local repository. In that
/// case, <see cref="DirectReference.Target"/> will return <c>null</c>.
/// </para>
/// </summary>
/// <param name="url">The url to list from.</param>
/// <param name="options">The options for the remote request.</param>
/// <returns>The references in the remote repository.</returns>
public virtual IEnumerable<Reference> ListReferences(string url, ListRemoteOptions options)
{
Ensure.ArgumentNotNull(url, "url");

return ListReferencesInternal(url, options);
}

/// <summary>
Expand All @@ -130,7 +191,12 @@ public virtual IEnumerable<Reference> ListReferences(string url)
{
Ensure.ArgumentNotNull(url, "url");

return ListReferencesInternal(url, null, new ProxyOptions());
var options = new ListRemoteOptions()
{
ProxyOptions = new ProxyOptions()
};

return ListReferencesInternal(url, options);
}

/// <summary>
Expand All @@ -148,8 +214,12 @@ public virtual IEnumerable<Reference> ListReferences(string url)
public virtual IEnumerable<Reference> ListReferences(string url, ProxyOptions proxyOptions)
{
Ensure.ArgumentNotNull(url, "url");
var options = new ListRemoteOptions()
{
ProxyOptions = proxyOptions
};

return ListReferencesInternal(url, null, proxyOptions);
return ListReferencesInternal(url, options);
}

/// <summary>
Expand All @@ -169,7 +239,13 @@ public virtual IEnumerable<Reference> ListReferences(string url, CredentialsHand
Ensure.ArgumentNotNull(url, "url");
Ensure.ArgumentNotNull(credentialsProvider, "credentialsProvider");

return ListReferencesInternal(url, credentialsProvider, new ProxyOptions());
var options = new ListRemoteOptions()
{
CredentialsProvider = credentialsProvider,
ProxyOptions = new ProxyOptions()
};

return ListReferencesInternal(url, options);
}

/// <summary>
Expand All @@ -190,21 +266,26 @@ public virtual IEnumerable<Reference> ListReferences(string url, CredentialsHand
Ensure.ArgumentNotNull(url, "url");
Ensure.ArgumentNotNull(credentialsProvider, "credentialsProvider");

return ListReferencesInternal(url, credentialsProvider, new ProxyOptions());
var options = new ListRemoteOptions()
{
CredentialsProvider = credentialsProvider,
ProxyOptions = new ProxyOptions()
};
return ListReferencesInternal(url, options);
}

private IEnumerable<Reference> ListReferencesInternal(string url, CredentialsHandler credentialsProvider, ProxyOptions proxyOptions)
private IEnumerable<Reference> ListReferencesInternal(string url, ListRemoteOptions options)
{
proxyOptions ??= new();
var proxyOptions = options?.ProxyOptions ?? new();

using RemoteHandle remoteHandle = BuildRemoteHandle(repository.Handle, url);
using var proxyOptionsWrapper = new GitProxyOptionsWrapper(proxyOptions.CreateGitProxyOptions());

GitRemoteCallbacks gitCallbacks = new GitRemoteCallbacks { version = 1 };

if (credentialsProvider != null)
if (options != null)
{
var callbacks = new RemoteCallbacks(credentialsProvider);
var callbacks = new RemoteCallbacks(options);
gitCallbacks = callbacks.GenerateCallbacks();
}

Expand Down
Loading
Loading