Skip to content

Mockable SmartSubtransportRegistration #2

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: transport_register
Choose a base branch
from
Open
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
1 change: 1 addition & 0 deletions LibGit2Sharp.Tests/LibGit2Sharp.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@
<Compile Include="ResetHeadFixture.cs" />
<Compile Include="FetchFixture.cs" />
<Compile Include="ResetIndexFixture.cs" />
<Compile Include="SmartSubtransportFixture.cs" />
<Compile Include="StatusFixture.cs" />
<Compile Include="TestHelpers\BaseFixture.cs" />
<Compile Include="BlobFixture.cs" />
Expand Down
261 changes: 261 additions & 0 deletions LibGit2Sharp.Tests/SmartSubtransportFixture.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,261 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using LibGit2Sharp.Tests.TestHelpers;
using Xunit;
using Xunit.Extensions;

namespace LibGit2Sharp.Tests
{
public class SmartSubtransportFixture : BaseFixture
{
[Theory]
[InlineData("http://github.com/libgit2/TestGitRepository")]
[InlineData("https://github.com/libgit2/TestGitRepository")]
public void CustomSmartSubtransportTest(string url)
{
string remoteName = "testRemote";

var scd = BuildSelfCleaningDirectory();
var repoPath = Repository.Init(scd.RootedDirectoryPath);

using ((IDisposable)LibGit2.RegisterSmartSubtransport(new MockSmartSubtransportRegistration("http://", 2)))
using ((IDisposable)LibGit2.RegisterSmartSubtransport(new MockSmartSubtransportRegistration("https://", 2)))
{
using (var repo = new Repository(scd.DirectoryPath))
{
Remote remote = repo.Network.Remotes.Add(remoteName, url);

// Set up structures for the expected results
// and verifying the RemoteUpdateTips callback.
TestRemoteInfo expectedResults = TestRemoteInfo.TestRemoteInstance;
ExpectedFetchState expectedFetchState = new ExpectedFetchState(remoteName);

// Add expected branch objects
foreach (KeyValuePair<string, ObjectId> kvp in expectedResults.BranchTips)
{
expectedFetchState.AddExpectedBranch(kvp.Key, ObjectId.Zero, kvp.Value);
}

// Add the expected tags
string[] expectedTagNames = { "blob", "commit_tree" };
foreach (string tagName in expectedTagNames)
{
TestRemoteInfo.ExpectedTagInfo expectedTagInfo = expectedResults.Tags[tagName];
expectedFetchState.AddExpectedTag(tagName, ObjectId.Zero, expectedTagInfo);
}

// Perform the actual fetch
repo.Network.Fetch(remote, new FetchOptions { OnUpdateTips = expectedFetchState.RemoteUpdateTipsHandler, TagFetchMode = TagFetchMode.Auto });

// Verify the expected
expectedFetchState.CheckUpdatedReferences(repo);
}
}
}

private class MockSmartSubtransportRegistration : SmartSubtransportRegistration<MockSmartSubtransport>, IDisposable
{
public MockSmartSubtransportRegistration(string prefix, int priority)
{
Prefix = prefix;
Priority = priority;
}

public void Dispose()
{
Free();
}
}

[RpcSmartSubtransport]
private class MockSmartSubtransport : SmartSubtransport
{
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 MockSmartSubtransportStream(this, endpointUrl, isPost, contentType);
}

private class MockSmartSubtransportStream : SmartSubtransportStream
{
private static int MAX_REDIRECTS = 5;

private MemoryStream postBuffer = new MemoryStream();
private Stream responseStream;

public MockSmartSubtransportStream(MockSmartSubtransport parent, string endpointUrl, bool isPost, string contentType)
: base(parent)
{
EndpointUrl = endpointUrl;
IsPost = isPost;
ContentType = contentType;
}

private string 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 static HttpWebRequest CreateWebRequest(string endpointUrl, bool isPost, string contentType)
{
HttpWebRequest webRequest = (HttpWebRequest)HttpWebRequest.Create(endpointUrl);
webRequest.UserAgent = "git/1.0 (libgit2 custom transport)";
webRequest.ServicePoint.Expect100Continue = false;
webRequest.AllowAutoRedirect = false;

if (isPost)
{
webRequest.Method = "POST";
webRequest.ContentType = contentType;
}

return webRequest;
}

private HttpWebResponse GetResponseWithRedirects()
{
HttpWebRequest request = CreateWebRequest(EndpointUrl, IsPost, ContentType);
HttpWebResponse response = null;

for (int i = 0; i < MAX_REDIRECTS; i++)
{
if (IsPost && postBuffer.Length > 0)
{
postBuffer.Seek(0, SeekOrigin.Begin);

using (Stream requestStream = request.GetRequestStream())
{
postBuffer.WriteTo(requestStream);
}
}

response = (HttpWebResponse)request.GetResponse();

if (response.StatusCode == HttpStatusCode.Moved || response.StatusCode == HttpStatusCode.Redirect)
{
request = CreateWebRequest(response.Headers["Location"], IsPost, ContentType);
continue;
}

break;
}

if (response == null)
{
throw new Exception("Too many redirects");
}

return response;
}

public override int Read(Stream dataStream, long length, out long readTotal)
{
byte[] buffer = new byte[4096];
readTotal = 0;

if (responseStream == null)
{
HttpWebResponse response = GetResponseWithRedirects();
responseStream = response.GetResponseStream();
}

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 Dispose()
{
if (responseStream != null)
{
responseStream.Dispose();
responseStream = null;
}

base.Dispose();
}
}
}
}
}
36 changes: 36 additions & 0 deletions LibGit2Sharp/Core/GitSmartSubtransport.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using System;
using System.Runtime.InteropServices;

namespace LibGit2Sharp.Core
{
[StructLayout(LayoutKind.Sequential)]
internal class GitSmartSubtransport
{
static GitSmartSubtransport()
{
GCHandleOffset = Marshal.OffsetOf(typeof(GitSmartSubtransport), "GCHandle").ToInt32();
}

public action_callback Action;
public close_callback Close;
public free_callback Free;

/* The libgit2 structure definition ends here. Subsequent fields are for libgit2sharp bookkeeping. */

public IntPtr GCHandle;

/* The following static fields are not part of the structure definition. */

public static int GCHandleOffset;

public delegate int action_callback(
out IntPtr stream,
IntPtr subtransport,
IntPtr url,
GitSmartSubtransportAction action);

public delegate int close_callback(IntPtr subtransport);

public delegate void free_callback(IntPtr subtransport);
}
}
16 changes: 16 additions & 0 deletions LibGit2Sharp/Core/GitSmartSubtransportRegistration.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using System;
using System.Runtime.InteropServices;

namespace LibGit2Sharp.Core
{
[StructLayout(LayoutKind.Sequential)]
internal class GitSmartSubtransportRegistration
{
public IntPtr SubtransportCallback;
public uint Rpc;

public delegate int create_callback(
out IntPtr subtransport,
IntPtr transport);
}
}
42 changes: 42 additions & 0 deletions LibGit2Sharp/Core/GitSmartSubtransportStream.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
using System;
using System.Runtime.InteropServices;

namespace LibGit2Sharp.Core
{
[StructLayout(LayoutKind.Sequential)]
internal class GitSmartSubtransportStream
{
static GitSmartSubtransportStream()
{
GCHandleOffset = Marshal.OffsetOf(typeof(GitSmartSubtransportStream), "GCHandle").ToInt32();
}

public IntPtr SmartTransport;

public read_callback Read;
public write_callback Write;
public free_callback Free;

/* The libgit2 structure definition ends here. Subsequent fields are for libgit2sharp bookkeeping. */

public IntPtr GCHandle;

/* The following static fields are not part of the structure definition. */

public static int GCHandleOffset;

public delegate int read_callback(
IntPtr stream,
IntPtr buffer,
UIntPtr buf_size,
out UIntPtr bytes_read);

public delegate int write_callback(
IntPtr stream,
IntPtr buffer,
UIntPtr len);

public delegate void free_callback(
IntPtr stream);
}
}
20 changes: 20 additions & 0 deletions LibGit2Sharp/Core/NativeMethods.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1396,6 +1396,26 @@ internal static extern int git_tag_delete(

internal delegate int git_transfer_progress_callback(ref GitTransferProgress stats, IntPtr payload);

internal delegate int git_transport_cb(out IntPtr transport, IntPtr remote, IntPtr payload);

[DllImport(libgit2)]
internal static extern int git_transport_register(
[MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string prefix,
uint priority,
IntPtr transport_cb,
IntPtr payload);

[DllImport(libgit2)]
internal static extern int git_transport_smart(
out IntPtr transport,
IntPtr remote,
IntPtr definition);

[DllImport(libgit2)]
internal static extern void git_transport_unregister(
[MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string prefix,
uint priority);

[DllImport(libgit2)]
internal static extern uint git_tree_entry_filemode(SafeHandle entry);

Expand Down
Loading