Skip to content

Commit

Permalink
Introduce blob FilteringOptions for content stream
Browse files Browse the repository at this point in the history
Get the content of a blob as it would be checked out to the
working directory via the filters.
  • Loading branch information
Edward Thomson committed Oct 18, 2013
1 parent 4d4ebe2 commit 31291fe
Show file tree
Hide file tree
Showing 9 changed files with 249 additions and 20 deletions.
87 changes: 84 additions & 3 deletions LibGit2Sharp.Tests/BlobFixture.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.IO;
using System;
using System.IO;
using System.Linq;
using System.Text;
using LibGit2Sharp.Tests.TestHelpers;
Expand All @@ -22,6 +23,28 @@ public void CanGetBlobAsText()
}
}

[Fact]
public void CanGetBlobAsFilteredText()
{
using (var repo = new Repository(BareTestRepoPath))
{
var blob = repo.Lookup<Blob>("a8233120f6ad708f843d861ce2b7228ec4e3dec6");

var text = blob.ContentAsText(new FilteringOptions("foo.txt"));

ConfigurationEntry<bool> autocrlf = repo.Config.Get<bool>("core.autocrlf");

if (autocrlf != null && autocrlf.Value)
{
Assert.Equal("hey there\r\n", text);
}
else
{
Assert.Equal("hey there\n", text);
}
}
}

[Theory]
[InlineData("ascii", 4, "31 32 33 34")]
[InlineData("utf-7", 4, "31 32 33 34")]
Expand Down Expand Up @@ -99,14 +122,59 @@ public void CanReadBlobStream()
{
var blob = repo.Lookup<Blob>("a8233120f6ad708f843d861ce2b7228ec4e3dec6");

using (var tr = new StreamReader(blob.ContentStream, Encoding.UTF8))
using (var tr = new StreamReader(blob.ContentStream(), Encoding.UTF8))
{
string content = tr.ReadToEnd();
Assert.Equal("hey there\n", content);
}
}
}

[Fact]
public void CanReadBlobFilteredStream()
{
using (var repo = new Repository(BareTestRepoPath))
{
var blob = repo.Lookup<Blob>("a8233120f6ad708f843d861ce2b7228ec4e3dec6");

using (var tr = new StreamReader(blob.ContentStream(new FilteringOptions("foo.txt")), Encoding.UTF8))
{
string content = tr.ReadToEnd();

ConfigurationEntry<bool> autocrlf = repo.Config.Get<bool>("core.autocrlf");

if (autocrlf != null && autocrlf.Value)
{
Assert.Equal("hey there\r\n", content);
}
else
{
Assert.Equal("hey there\n", content);
}
}
}
}

[Fact]
public void CanReadBlobFilteredStreamOfUnmodifiedBinary()
{
var binaryContent = new byte[] { 0, 1, 2, 3, 4, 5 };

string path = CloneBareTestRepo();
using (var repo = new Repository(path))
{
using (var stream = new MemoryStream(binaryContent))
{
Blob blob = repo.ObjectDatabase.CreateBlob(stream);

using (var filtered = blob.ContentStream(new FilteringOptions("foo.txt")))
{
Assert.True(StreamEquals(stream, filtered));
}
}
}
}

public static void CopyStream(Stream input, Stream output)
{
// Reused from the following Stack Overflow post with permission
Expand All @@ -120,6 +188,19 @@ public static void CopyStream(Stream input, Stream output)
}
}

public static bool StreamEquals(Stream one, Stream two)
{
int onebyte, twobyte;

while ((onebyte = one.ReadByte()) >= 0 && (twobyte = two.ReadByte()) >= 0)
{
if (onebyte != twobyte)
return false;
}

return true;
}

[Fact]
public void CanStageAFileGeneratedFromABlobContentStream()
{
Expand All @@ -143,7 +224,7 @@ public void CanStageAFileGeneratedFromABlobContentStream()

var blob = repo.Lookup<Blob>(entry.Id.Sha);

using (Stream stream = blob.ContentStream)
using (Stream stream = blob.ContentStream())
using (Stream file = File.OpenWrite(Path.Combine(repo.Info.WorkingDirectory, "small.fromblob.txt")))
{
CopyStream(stream, file);
Expand Down
18 changes: 13 additions & 5 deletions LibGit2Sharp/Blob.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,20 @@ public virtual byte[] Content
/// <summary>
/// Gets the blob content in a <see cref="Stream"/>.
/// </summary>
public virtual Stream ContentStream
public virtual Stream ContentStream()
{
get
{
return Proxy.git_blob_rawcontent_stream(repo.Handle, Id, Size);
}
return Proxy.git_blob_rawcontent_stream(repo.Handle, Id, Size);
}

/// <summary>
/// Gets the blob content in a <see cref="Stream"/> as it would be
/// checked out to the working directory.
/// <param name="filteringOptions">Parameter controlling content filtering behavior</param>
/// </summary>
public virtual Stream ContentStream(FilteringOptions filteringOptions)
{
Ensure.ArgumentNotNull(filteringOptions, "filteringOptions");
return Proxy.git_blob_filtered_content_stream(repo.Handle, Id, filteringOptions.HintPath, false);
}
}
}
23 changes: 22 additions & 1 deletion LibGit2Sharp/BlobExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,28 @@ public static string ContentAsText(this Blob blob, Encoding encoding = null)
{
Ensure.ArgumentNotNull(blob, "blob");

using (var reader = new StreamReader(blob.ContentStream, encoding ?? Encoding.UTF8, encoding == null))
using (var reader = new StreamReader(blob.ContentStream(), encoding ?? Encoding.UTF8, encoding == null))
{
return reader.ReadToEnd();
}
}

/// <summary>
/// Gets the blob content as it would be checked out to the
/// working directory, decoded with the specified encoding,
/// or according to byte order marks, with UTF8 as fallback,
/// if <paramref name="encoding"/> is null.
/// </summary>
/// <param name="blob">The blob for which the content will be returned.</param>
/// <param name="filteringOptions">Parameter controlling content filtering behavior</param>
/// <param name="encoding">The encoding of the text. (default: detected or UTF8)</param>
/// <returns>Blob content as text.</returns>
public static string ContentAsText(this Blob blob, FilteringOptions filteringOptions, Encoding encoding = null)
{
Ensure.ArgumentNotNull(blob, "blob");
Ensure.ArgumentNotNull(filteringOptions, "filteringOptions");

using(var reader = new StreamReader(blob.ContentStream(filteringOptions), encoding ?? Encoding.UTF8, encoding == null))
{
return reader.ReadToEnd();
}
Expand Down
18 changes: 18 additions & 0 deletions LibGit2Sharp/Core/GitBuf.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using System;
using System.Runtime.InteropServices;

namespace LibGit2Sharp.Core.Handles
{
[StructLayout(LayoutKind.Sequential)]
internal class GitBuf : IDisposable
{
public IntPtr ptr;
public UIntPtr asize;
public UIntPtr size;

public void Dispose()
{
Proxy.git_buf_free(this);
}
}
}
10 changes: 10 additions & 0 deletions LibGit2Sharp/Core/NativeMethods.cs
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,13 @@ private static bool IsRunningOnLinux()
source_callback fileCallback,
IntPtr data);

[DllImport(libgit2)]
internal static extern int git_blob_filtered_content(
GitBuf buf,
GitObjectSafeHandle blob,
[MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictFilePathMarshaler))] FilePath as_path,
[MarshalAs(UnmanagedType.Bool)] bool check_for_binary_data);

[DllImport(libgit2)]
internal static extern IntPtr git_blob_rawcontent(GitObjectSafeHandle blob);

Expand Down Expand Up @@ -182,6 +189,9 @@ private static bool IsRunningOnLinux()
RepositorySafeHandle repo,
[MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string referenceName);

[DllImport(libgit2)]
internal static extern void git_buf_free(GitBuf buf);

[DllImport(libgit2)]
internal static extern int git_checkout_tree(
RepositorySafeHandle repo,
Expand Down
26 changes: 25 additions & 1 deletion LibGit2Sharp/Core/Proxy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,20 @@ public static ObjectId git_blob_create_fromfile(RepositorySafeHandle repo, FileP
}
}

public static UnmanagedMemoryStream git_blob_filtered_content_stream(RepositorySafeHandle repo, ObjectId id, FilePath path, bool check_for_binary_data)
{
var buf = new GitBuf();
var handle = new ObjectSafeWrapper(id, repo).ObjectPtr;

return new RawContentStream(handle, h =>
{
Ensure.ZeroResult(NativeMethods.git_blob_filtered_content(buf, h, path, check_for_binary_data));
return buf.ptr;
},
h => (long)buf.size,
new[] { buf });
}

public static byte[] git_blob_rawcontent(RepositorySafeHandle repo, ObjectId id, int size)
{
using (var obj = new ObjectSafeWrapper(id, repo))
Expand All @@ -85,7 +99,8 @@ public static byte[] git_blob_rawcontent(RepositorySafeHandle repo, ObjectId id,

public static UnmanagedMemoryStream git_blob_rawcontent_stream(RepositorySafeHandle repo, ObjectId id, Int64 size)
{
return new RawContentStream(id, repo, NativeMethods.git_blob_rawcontent, size);
var handle = new ObjectSafeWrapper(id, repo).ObjectPtr;
return new RawContentStream(handle, NativeMethods.git_blob_rawcontent, h => size);
}

public static Int64 git_blob_rawsize(GitObjectSafeHandle obj)
Expand Down Expand Up @@ -188,6 +203,15 @@ public static string git_branch_upstream_name(RepositorySafeHandle handle, strin

#endregion

#region git_buf_

public static void git_buf_free(GitBuf buf)
{
NativeMethods.git_buf_free(buf);
}

#endregion

#region git_checkout_

public static void git_checkout_tree(
Expand Down
58 changes: 48 additions & 10 deletions LibGit2Sharp/Core/RawContentStream.cs
Original file line number Diff line number Diff line change
@@ -1,30 +1,68 @@
using System;
using System.Collections.Generic;
using System.IO;
using LibGit2Sharp.Core.Handles;

namespace LibGit2Sharp.Core
{
internal class RawContentStream : UnmanagedMemoryStream
{
private readonly ObjectSafeWrapper wrapper;
private readonly GitObjectSafeHandle handle;
private readonly ICollection<IDisposable> linkedResources;

internal RawContentStream(ObjectId id, RepositorySafeHandle repo,
Func<GitObjectSafeHandle, IntPtr> bytePtrProvider, long length)
: this(new ObjectSafeWrapper(id, repo), bytePtrProvider, length)
internal unsafe RawContentStream(
GitObjectSafeHandle handle,
Func<GitObjectSafeHandle, IntPtr> bytePtrProvider,
Func<GitObjectSafeHandle, long> sizeProvider,
ICollection<IDisposable> linkedResources = null)
: base((byte*)Wrap(handle, bytePtrProvider, linkedResources).ToPointer(),
Wrap(handle, sizeProvider, linkedResources))
{
this.handle = handle;
this.linkedResources = linkedResources;
}

unsafe RawContentStream(ObjectSafeWrapper wrapper,
Func<GitObjectSafeHandle, IntPtr> bytePtrProvider, long length)
: base((byte*)bytePtrProvider(wrapper.ObjectPtr).ToPointer(), length)
private static T Wrap<T>(
GitObjectSafeHandle handle,
Func<GitObjectSafeHandle, T> provider,
IEnumerable<IDisposable> linkedResources)
{
this.wrapper = wrapper;
T value;

try
{
value = provider(handle);
}
catch
{
Dispose(handle, linkedResources);
throw;
}

return value;
}

private static void Dispose(
GitObjectSafeHandle handle,
IEnumerable<IDisposable> linkedResources)
{
handle.SafeDispose();

if (linkedResources == null)
{
return;
}

foreach (IDisposable linkedResource in linkedResources)
{
linkedResource.Dispose();
}
}

protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
wrapper.SafeDispose();
Dispose(handle, linkedResources);
}
}
}
}
27 changes: 27 additions & 0 deletions LibGit2Sharp/FilteringOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using LibGit2Sharp.Core;

namespace LibGit2Sharp
{
/// <summary>
/// Allows callers to specify how blob content filters will be applied.
/// </summary>
public sealed class FilteringOptions
{
/// <summary>
/// Initializes a new instance of the <see cref="FilteringOptions"/> class.
/// </summary>
/// <param name="hintPath">The path that a file would be checked out as</param>
public FilteringOptions(string hintPath)
{
Ensure.ArgumentNotNull(hintPath, "hintPath");

this.HintPath = hintPath;
}

/// <summary>
/// The path to "hint" to the filters will be used to apply
/// attributes.
/// </summary>
public string HintPath { get; private set; }
}
}
2 changes: 2 additions & 0 deletions LibGit2Sharp/LibGit2Sharp.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@
<Compile Include="CompareOptions.cs" />
<Compile Include="Core\EncodingMarshaler.cs" />
<Compile Include="PushOptions.cs" />
<Compile Include="Core\GitBuf.cs" />
<Compile Include="FilteringOptions.cs" />
<Compile Include="UnbornBranchException.cs" />
<Compile Include="LockedFileException.cs" />
<Compile Include="Core\GitRepositoryInitOptions.cs" />
Expand Down

0 comments on commit 31291fe

Please sign in to comment.