Skip to content
Merged
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
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 @@ internal static extern int git_blob_create_fromchunks(
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 @@ internal static extern int git_branch_upstream_name(
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