From 29efc69f0ac78e2b44e64e7de3de6a845e7ec4a7 Mon Sep 17 00:00:00 2001 From: Brandon Ording Date: Sun, 19 Sep 2021 14:22:48 -0400 Subject: [PATCH] Use native HTTPS support --- LibGit2Sharp.Tests/CloneFixture.cs | 157 +++++----- LibGit2Sharp.Tests/GlobalSettingsFixture.cs | 1 + .../Core/ManagedHttpSmartSubtransport.cs | 270 ------------------ LibGit2Sharp/Core/NativeMethods.cs | 24 +- LibGit2Sharp/GlobalSettings.cs | 143 +--------- 5 files changed, 90 insertions(+), 505 deletions(-) delete mode 100644 LibGit2Sharp/Core/ManagedHttpSmartSubtransport.cs diff --git a/LibGit2Sharp.Tests/CloneFixture.cs b/LibGit2Sharp.Tests/CloneFixture.cs index 2408dad05..0fefabbf6 100644 --- a/LibGit2Sharp.Tests/CloneFixture.cs +++ b/LibGit2Sharp.Tests/CloneFixture.cs @@ -5,7 +5,6 @@ using LibGit2Sharp.Handlers; using LibGit2Sharp.Tests.TestHelpers; using Xunit; -using Xunit.Extensions; namespace LibGit2Sharp.Tests { @@ -110,9 +109,9 @@ public void CanCloneBarely(string url) var scd = BuildSelfCleaningDirectory(); string clonedRepoPath = Repository.Clone(url, scd.DirectoryPath, new CloneOptions - { - IsBare = true - }); + { + IsBare = true + }); using (var repo = new Repository(clonedRepoPath)) { @@ -195,7 +194,7 @@ public void CanCloneWithCredentials() } } - static Credentials CreateUsernamePasswordCredentials (string user, string pass, bool secure) + static Credentials CreateUsernamePasswordCredentials(string user, string pass, bool secure) { if (secure) { @@ -222,7 +221,7 @@ public void CanCloneFromBBWithCredentials(string url, string user, string pass, string clonedRepoPath = Repository.Clone(url, scd.DirectoryPath, new CloneOptions() { - CredentialsProvider = (_url, _user, _cred) => CreateUsernamePasswordCredentials (user, pass, secure) + CredentialsProvider = (_url, _user, _cred) => CreateUsernamePasswordCredentials(user, pass, secure) }); using (var repo = new Repository(clonedRepoPath)) @@ -237,65 +236,69 @@ public void CanCloneFromBBWithCredentials(string url, string user, string pass, } } - //[SkippableTheory] - //[InlineData("https://github.com/libgit2/TestGitRepository.git", "github.com", typeof(CertificateX509))] - //[InlineData("git@github.com:libgit2/TestGitRepository.git", "github.com", typeof(CertificateSsh))] - //public void CanInspectCertificateOnClone(string url, string hostname, Type certType) - //{ - // var scd = BuildSelfCleaningDirectory(); - - // InconclusiveIf( - // () => - // certType == typeof (CertificateSsh) && !GlobalSettings.Version.Features.HasFlag(BuiltInFeatures.Ssh), - // "SSH not supported"); - - // bool wasCalled = false; - // bool checksHappy = false; - - // var options = new CloneOptions { - // CertificateCheck = (cert, valid, host) => { - // wasCalled = true; - - // Assert.Equal(hostname, host); - // Assert.Equal(certType, cert.GetType()); - - // if (certType == typeof(CertificateX509)) { - // Assert.True(valid); - // var x509 = ((CertificateX509)cert).Certificate; - // // we get a string with the different fields instead of a structure, so... - // Assert.Contains("CN=github.com,", x509.Subject); - // checksHappy = true; - // return false; - // } - - // if (certType == typeof(CertificateSsh)) { - // var hostkey = (CertificateSsh)cert; - // Assert.True(hostkey.HasMD5); - // /* - // * Once you've connected and thus your ssh has stored the hostkey, - // * you can get the hostkey for a host with - // * - // * ssh-keygen -F github.com -l | tail -n 1 | cut -d ' ' -f 2 | tr -d ':' - // * - // * though GitHub's hostkey won't change anytime soon. - // */ - // Assert.Equal("1627aca576282d36631b564debdfa648", - // BitConverter.ToString(hostkey.HashMD5).ToLower().Replace("-", "")); - // checksHappy = true; - // return false; - // } - - // return false; - // }, - // }; - - // Assert.Throws(() => - // Repository.Clone(url, scd.DirectoryPath, options) - // ); - - // Assert.True(wasCalled); - // Assert.True(checksHappy); - //} + [SkippableTheory] + [InlineData("https://github.com/libgit2/TestGitRepository.git", "github.com", typeof(CertificateX509))] + [InlineData("git@github.com:libgit2/TestGitRepository.git", "github.com", typeof(CertificateSsh))] + public void CanInspectCertificateOnClone(string url, string hostname, Type certType) + { + var scd = BuildSelfCleaningDirectory(); + + InconclusiveIf( + () => + certType == typeof(CertificateSsh) && !GlobalSettings.Version.Features.HasFlag(BuiltInFeatures.Ssh), + "SSH not supported"); + + bool wasCalled = false; + bool checksHappy = false; + + var options = new CloneOptions + { + CertificateCheck = (cert, valid, host) => + { + wasCalled = true; + + Assert.Equal(hostname, host); + Assert.Equal(certType, cert.GetType()); + + if (certType == typeof(CertificateX509)) + { + Assert.True(valid); + var x509 = ((CertificateX509)cert).Certificate; + // we get a string with the different fields instead of a structure, so... + Assert.Contains("CN=github.com,", x509.Subject); + checksHappy = true; + return false; + } + + if (certType == typeof(CertificateSsh)) + { + var hostkey = (CertificateSsh)cert; + Assert.True(hostkey.HasMD5); + /* + * Once you've connected and thus your ssh has stored the hostkey, + * you can get the hostkey for a host with + * + * ssh-keygen -F github.com -l | tail -n 1 | cut -d ' ' -f 2 | tr -d ':' + * + * though GitHub's hostkey won't change anytime soon. + */ + Assert.Equal("1627aca576282d36631b564debdfa648", + BitConverter.ToString(hostkey.HashMD5).ToLower().Replace("-", "")); + checksHappy = true; + return false; + } + + return false; + }, + }; + + Assert.Throws(() => + Repository.Clone(url, scd.DirectoryPath, options) + ); + + Assert.True(wasCalled); + Assert.True(checksHappy); + } [Theory] [InlineData("git://github.com/libgit2/TestGitRepository")] @@ -441,7 +444,7 @@ public void CanRecursivelyCloneSubmodules() string clonedRepoPath = Repository.Clone(uri.AbsolutePath, scd.DirectoryPath, options); string workDirPath; - using(Repository repo = new Repository(clonedRepoPath)) + using (Repository repo = new Repository(clonedRepoPath)) { workDirPath = repo.Info.WorkingDirectory.TrimEnd(new char[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar }); } @@ -452,14 +455,14 @@ public void CanRecursivelyCloneSubmodules() Dictionary expectedCallbackInfo = new Dictionary(); expectedCallbackInfo.Add(workDirPath, new CloneCallbackInfo() - { - RecursionDepth = 0, - RemoteUrl = uri.AbsolutePath, - StartingWorkInRepositoryCalled = true, - FinishedWorkInRepositoryCalled = true, - CheckoutProgressCalled = true, - RemoteRefUpdateCalled = true, - }); + { + RecursionDepth = 0, + RemoteUrl = uri.AbsolutePath, + StartingWorkInRepositoryCalled = true, + FinishedWorkInRepositoryCalled = true, + CheckoutProgressCalled = true, + RemoteRefUpdateCalled = true, + }); expectedCallbackInfo.Add(Path.Combine(workDirPath, relativeSubmodulePath), new CloneCallbackInfo() { @@ -486,7 +489,7 @@ public void CanRecursivelyCloneSubmodules() } // Verify the state of the submodule - using(Repository repo = new Repository(clonedRepoPath)) + using (Repository repo = new Repository(clonedRepoPath)) { var sm = repo.Submodules[relativeSubmodulePath]; Assert.True(sm.RetrieveStatus().HasFlag(SubmoduleStatus.InWorkDir | @@ -533,7 +536,7 @@ public void CanCancelRecursiveClone() { Repository.Clone(uri.AbsolutePath, scd.DirectoryPath, options); } - catch(RecurseSubmodulesException ex) + catch (RecurseSubmodulesException ex) { Assert.NotNull(ex.InnerException); Assert.Equal(typeof(UserCancelledException), ex.InnerException.GetType()); @@ -541,7 +544,7 @@ public void CanCancelRecursiveClone() } // Verify that the submodule was not initialized. - using(Repository repo = new Repository(clonedRepoPath)) + using (Repository repo = new Repository(clonedRepoPath)) { var submoduleStatus = repo.Submodules[relativeSubmodulePath].RetrieveStatus(); Assert.Equal(SubmoduleStatus.InConfig | SubmoduleStatus.InHead | SubmoduleStatus.InIndex | SubmoduleStatus.WorkDirUninitialized, diff --git a/LibGit2Sharp.Tests/GlobalSettingsFixture.cs b/LibGit2Sharp.Tests/GlobalSettingsFixture.cs index 6026f267a..55260a6f5 100644 --- a/LibGit2Sharp.Tests/GlobalSettingsFixture.cs +++ b/LibGit2Sharp.Tests/GlobalSettingsFixture.cs @@ -15,6 +15,7 @@ public void CanGetMinimumCompiledInFeatures() BuiltInFeatures features = GlobalSettings.Version.Features; Assert.True(features.HasFlag(BuiltInFeatures.Threads)); + Assert.True(features.HasFlag(BuiltInFeatures.Https)); } [Fact] diff --git a/LibGit2Sharp/Core/ManagedHttpSmartSubtransport.cs b/LibGit2Sharp/Core/ManagedHttpSmartSubtransport.cs deleted file mode 100644 index 88eced880..000000000 --- a/LibGit2Sharp/Core/ManagedHttpSmartSubtransport.cs +++ /dev/null @@ -1,270 +0,0 @@ -using System; -using System.IO; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Security.Authentication; - -namespace LibGit2Sharp.Core -{ - internal class ManagedHttpSmartSubtransport : RpcSmartSubtransport - { - protected override SmartSubtransportStream Action(string url, GitSmartSubtransportAction action) - { - string endpointUrl, contentType = null; - bool isPost = false; - - switch (action) - { - case GitSmartSubtransportAction.UploadPackList: - endpointUrl = string.Concat(url, "/info/refs?service=git-upload-pack"); - break; - - case GitSmartSubtransportAction.UploadPack: - endpointUrl = string.Concat(url, "/git-upload-pack"); - contentType = "application/x-git-upload-pack-request"; - isPost = true; - break; - - case GitSmartSubtransportAction.ReceivePackList: - endpointUrl = string.Concat(url, "/info/refs?service=git-receive-pack"); - break; - - case GitSmartSubtransportAction.ReceivePack: - endpointUrl = string.Concat(url, "/git-receive-pack"); - contentType = "application/x-git-receive-pack-request"; - isPost = true; - break; - - default: - throw new InvalidOperationException(); - } - - return new ManagedHttpSmartSubtransportStream(this, endpointUrl, isPost, contentType); - } - - private class ManagedHttpSmartSubtransportStream : SmartSubtransportStream - { - private static int MAX_REDIRECTS = 7; - -#if NETCOREAPP - private static readonly SocketsHttpHandler httpHandler; -#else - private static readonly HttpClientHandler httpHandler; -#endif - - private static readonly CredentialCache credentialCache; - - private MemoryStream postBuffer = new MemoryStream(); - private HttpResponseMessage response; - private Stream responseStream; - - static ManagedHttpSmartSubtransportStream() - { -#if NETCOREAPP - httpHandler = new SocketsHttpHandler(); - httpHandler.PooledConnectionLifetime = TimeSpan.FromMinutes(5); -#else - httpHandler = new HttpClientHandler(); - httpHandler.SslProtocols |= SslProtocols.Tls12; -#endif - - httpHandler.AllowAutoRedirect = false; - - credentialCache = new CredentialCache(); - httpHandler.Credentials = credentialCache; - } - - public ManagedHttpSmartSubtransportStream(ManagedHttpSmartSubtransport parent, string endpointUrl, bool isPost, string contentType) - : base(parent) - { - EndpointUrl = new Uri(endpointUrl); - IsPost = isPost; - ContentType = contentType; - } - - private HttpClient CreateHttpClient(HttpMessageHandler handler) - { - return new HttpClient(handler, false) - { - DefaultRequestHeaders = - { - // This worked fine when it was on, but git.exe doesn't specify this header, so we don't either. - ExpectContinue = false, - }, - }; - } - - private Uri EndpointUrl { get; set; } - - private bool IsPost { get; set; } - - private string ContentType { get; set; } - - public override int Write(Stream dataStream, long length) - { - byte[] buffer = new byte[4096]; - long writeTotal = 0; - - while (length > 0) - { - int readLen = dataStream.Read(buffer, 0, (int)Math.Min(buffer.Length, length)); - - if (readLen == 0) - { - break; - } - - postBuffer.Write(buffer, 0, readLen); - length -= readLen; - writeTotal += readLen; - } - - if (writeTotal < length) - { - throw new EndOfStreamException("Could not write buffer (short read)"); - } - - return 0; - } - - private string GetUserAgent() - { - string userAgent = GlobalSettings.GetUserAgent(); - - if (string.IsNullOrEmpty(userAgent)) - { - userAgent = "LibGit2Sharp " + GlobalSettings.Version.InformationalVersion; - } - - return userAgent; - } - - private HttpRequestMessage CreateRequest(Uri endpointUrl, bool isPost) - { - var verb = isPost ? new HttpMethod("POST") : new HttpMethod("GET"); - var request = new HttpRequestMessage(verb, endpointUrl); - request.Headers.Add("User-Agent", $"git/2.0 ({GetUserAgent()})"); - request.Headers.Remove("Expect"); - - return request; - } - - private HttpResponseMessage GetResponseWithRedirects() - { - var url = EndpointUrl; - int retries; - - for (retries = 0; ; retries++) - { - using (var httpClient = CreateHttpClient(httpHandler)) - { - var request = CreateRequest(url, IsPost); - - if (retries > MAX_REDIRECTS) - { - throw new Exception("too many redirects or authentication replays"); - } - - if (IsPost && postBuffer.Length > 0) - { - var bufferDup = new MemoryStream(postBuffer.GetBuffer(), 0, (int)postBuffer.Length); - - request.Content = new StreamContent(bufferDup); - request.Content.Headers.Add("Content-Type", ContentType); - } - - var response = httpClient.SendAsync(request, HttpCompletionOption.ResponseContentRead).GetAwaiter().GetResult(); - - if (response.StatusCode == HttpStatusCode.OK) - { - return response; - } - else if (response.StatusCode == HttpStatusCode.Unauthorized) - { - int ret = SmartTransport.AcquireCredentials(out Credentials cred, null, typeof(UsernamePasswordCredentials)); - - if (ret != 0) - { - throw new InvalidOperationException("authentication cancelled"); - } - - var scheme = response.Headers.WwwAuthenticate.First().Scheme; - - if (cred is DefaultCredentials) - { - lock (credentialCache) - { - credentialCache.Add(url, scheme, CredentialCache.DefaultNetworkCredentials); - } - } - else if (cred is UsernamePasswordCredentials userpass) - { - lock (credentialCache) - { - credentialCache.Add(url, scheme, new NetworkCredential(userpass.Username, userpass.Password)); - } - } - - continue; - } - else if (response.StatusCode == HttpStatusCode.Moved || response.StatusCode == HttpStatusCode.Redirect) - { - url = new Uri(response.Headers.GetValues("Location").First()); - continue; - } - - throw new Exception(string.Format("unexpected HTTP response: {0}", response.StatusCode)); - } - } - - throw new Exception("too many redirects or authentication replays"); - } - - public override int Read(Stream dataStream, long length, out long readTotal) - { - byte[] buffer = new byte[4096]; - readTotal = 0; - - if (responseStream == null) - { - response = GetResponseWithRedirects(); - responseStream = response.Content.ReadAsStreamAsync().GetAwaiter().GetResult(); - } - - while (length > 0) - { - int readLen = responseStream.Read(buffer, 0, (int)Math.Min(buffer.Length, length)); - - if (readLen == 0) - { - break; - } - - dataStream.Write(buffer, 0, readLen); - readTotal += readLen; - length -= readLen; - } - - return 0; - } - - protected override void Free() - { - if (responseStream != null) - { - responseStream.Dispose(); - responseStream = null; - } - - if (response != null) - { - response.Dispose(); - response = null; - } - - base.Free(); - } - } - } -} diff --git a/LibGit2Sharp/Core/NativeMethods.cs b/LibGit2Sharp/Core/NativeMethods.cs index f7c9dbf26..04be0e683 100644 --- a/LibGit2Sharp/Core/NativeMethods.cs +++ b/LibGit2Sharp/Core/NativeMethods.cs @@ -26,9 +26,6 @@ internal static class NativeMethods private static NativeShutdownObject shutdownObject; #pragma warning restore 0414 - private static SmartSubtransportRegistration httpSubtransportRegistration; - private static SmartSubtransportRegistration httpsSubtransportRegistration; - static NativeMethods() { if (Platform.IsRunningOnNetFramework() || Platform.IsRunningOnNetCore()) @@ -106,7 +103,7 @@ private static bool TryUseNativeLibrary() new Type[] { typeof(string), typeof(Assembly), typeof(DllImportSearchPath?), typeof(IntPtr).MakeByRefType() })?.CreateDelegate(typeof(TryLoadLibraryByNameDelegate)); var tryLoadLibraryByPath = (TryLoadLibraryByPathDelegate)nativeLibraryType?.GetMethod("TryLoad", new Type[] { typeof(string), typeof(IntPtr).MakeByRefType() })?.CreateDelegate(typeof(TryLoadLibraryByPathDelegate)); - MethodInfo setDllImportResolver = nativeLibraryType?.GetMethod("SetDllImportResolver", new Type[] { typeof(Assembly), dllImportResolverType}); + MethodInfo setDllImportResolver = nativeLibraryType?.GetMethod("SetDllImportResolver", new Type[] { typeof(Assembly), dllImportResolverType }); if (dllImportResolverType == null || nativeLibraryType == null || @@ -196,11 +193,10 @@ private static void InitializeNativeLibrary() shutdownObject = new NativeShutdownObject(); } - // Configure the .NET HTTP(S) mechanism on the first initialization of the library in the current process. + // Configure the OpenSSL locking on the first initialization of the library in the current process. if (initCounter == 1) { - httpSubtransportRegistration = GlobalSettings.RegisterDefaultSmartSubtransport("http"); - httpsSubtransportRegistration = GlobalSettings.RegisterDefaultSmartSubtransport("https"); + git_openssl_set_locking(); } } @@ -209,16 +205,6 @@ private sealed class NativeShutdownObject : CriticalFinalizerObject { ~NativeShutdownObject() { - if (httpSubtransportRegistration != null) - { - GlobalSettings.UnregisterDefaultSmartSubtransport(httpSubtransportRegistration); - } - - if (httpsSubtransportRegistration != null) - { - GlobalSettings.UnregisterDefaultSmartSubtransport(httpsSubtransportRegistration); - } - git_libgit2_shutdown(); } } @@ -452,7 +438,7 @@ internal static extern unsafe int git_commit_create_from_ids( [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string message, ref GitOid tree, UIntPtr parentCount, - [MarshalAs(UnmanagedType.LPArray)] [In] IntPtr[] parents); + [MarshalAs(UnmanagedType.LPArray)][In] IntPtr[] parents); [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] internal static extern unsafe int git_commit_create_buffer( @@ -2111,7 +2097,7 @@ internal static extern unsafe int git_worktree_unlock( git_worktree* worktree); [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] - internal static extern unsafe int git_worktree_add ( + internal static extern unsafe int git_worktree_add( out git_worktree* reference, git_repository* repo, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string name, diff --git a/LibGit2Sharp/GlobalSettings.cs b/LibGit2Sharp/GlobalSettings.cs index 1e5d17a19..00b031ca0 100644 --- a/LibGit2Sharp/GlobalSettings.cs +++ b/LibGit2Sharp/GlobalSettings.cs @@ -22,14 +22,6 @@ public static class GlobalSettings private static bool nativeLibraryPathLocked; private static string nativeLibraryDefaultPath; - internal class SmartSubtransportData - { - internal bool isCustom; - internal SmartSubtransportRegistrationData defaultSubtransport; - } - - private static readonly Dictionary smartSubtransportData = new Dictionary(); - static GlobalSettings() { bool netFX = Platform.IsRunningOnNetFramework(); @@ -81,48 +73,6 @@ private static string GetExecutingAssemblyDirectory() /// public static Version Version => version.Value; - private static SmartSubtransportData GetOrCreateSmartSubtransportData(string scheme) - { - Ensure.ArgumentNotNull(scheme, "scheme"); - - lock (smartSubtransportData) - { - if (!smartSubtransportData.ContainsKey(scheme)) - { - smartSubtransportData[scheme] = new SmartSubtransportData(); - } - - return smartSubtransportData[scheme]; - } - } - - internal static SmartSubtransportRegistration RegisterDefaultSmartSubtransport(string scheme) - where T : SmartSubtransport, new() - { - Ensure.ArgumentNotNull(scheme, "scheme"); - - lock (smartSubtransportData) - { - var data = GetOrCreateSmartSubtransportData(scheme); - - if (data.defaultSubtransport != null) - { - throw new Exception(string.Format("A default subtransport is already configured for {0}", scheme)); - } - - var registration = new SmartSubtransportRegistration(scheme); - - if (!data.isCustom) - { - RegisterSmartSubtransportInternal(registration); - } - - data.defaultSubtransport = registration; - - return registration; - } - } - /// /// Registers a new as a custom /// smart-protocol transport with libgit2. Any Git remote with @@ -141,53 +91,21 @@ public static SmartSubtransportRegistration RegisterSmartSubtransport(stri { Ensure.ArgumentNotNull(scheme, "scheme"); - lock (smartSubtransportData) - { - var data = GetOrCreateSmartSubtransportData(scheme); - - if (data.isCustom) - { - throw new EntryExistsException(string.Format("A smart subtransport is already registered for {0}", scheme)); - } - - if (data.defaultSubtransport != null) - { - Proxy.git_transport_unregister(scheme); - } - - var previousValue = data.isCustom; - data.isCustom = true; - - var registration = new SmartSubtransportRegistration(scheme); - - try - { - RegisterSmartSubtransportInternal(registration); - } - catch - { - data.isCustom = previousValue; - throw; - } - - return registration; - } - } + var registration = new SmartSubtransportRegistration(scheme); - private static void RegisterSmartSubtransportInternal(SmartSubtransportRegistration registration) - where T : SmartSubtransport, new() - { try { Proxy.git_transport_register(registration.Scheme, registration.FunctionPointer, registration.RegistrationPointer); } - catch + catch (Exception) { registration.Free(); throw; } + + return registration; } /// @@ -201,59 +119,6 @@ public static void UnregisterSmartSubtransport(SmartSubtransportRegistration< { Ensure.ArgumentNotNull(registration, "registration"); - var scheme = registration.Scheme; - - lock (smartSubtransportData) - { - var data = GetOrCreateSmartSubtransportData(scheme); - - if (!data.isCustom) - { - throw new NotFoundException(string.Format("No smart subtransport has been registered for {0}", scheme)); - } - - UnregisterSmartSubtransportInternal(registration); - - data.isCustom = false; - - if (data.defaultSubtransport != null) - { - var defaultRegistration = data.defaultSubtransport; - - Proxy.git_transport_register(defaultRegistration.Scheme, - defaultRegistration.FunctionPointer, - defaultRegistration.RegistrationPointer); - } - } - } - - internal static void UnregisterDefaultSmartSubtransport(SmartSubtransportRegistration registration) - where T : SmartSubtransport, new() - { - Ensure.ArgumentNotNull(registration, "registration"); - - var scheme = registration.Scheme; - - lock (smartSubtransportData) - { - if (!smartSubtransportData.ContainsKey(scheme)) - { - throw new Exception(string.Format("No default smart subtransport has been registered for {0}", scheme)); - } - - if (registration != smartSubtransportData[scheme].defaultSubtransport) - { - throw new Exception(string.Format("The given smart subtransport is not the default for {0}", scheme)); - } - - smartSubtransportData.Remove(scheme); - UnregisterSmartSubtransportInternal(registration); - } - } - - private static void UnregisterSmartSubtransportInternal(SmartSubtransportRegistration registration) - where T : SmartSubtransport, new() - { Proxy.git_transport_unregister(registration.Scheme); registration.Free(); }