Skip to content

Commit 31291fe

Browse files
author
Edward Thomson
committed
Introduce blob FilteringOptions for content stream
Get the content of a blob as it would be checked out to the working directory via the filters.
1 parent 4d4ebe2 commit 31291fe

File tree

9 files changed

+249
-20
lines changed

9 files changed

+249
-20
lines changed

LibGit2Sharp.Tests/BlobFixture.cs

+84-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using System.IO;
1+
using System;
2+
using System.IO;
23
using System.Linq;
34
using System.Text;
45
using LibGit2Sharp.Tests.TestHelpers;
@@ -22,6 +23,28 @@ public void CanGetBlobAsText()
2223
}
2324
}
2425

26+
[Fact]
27+
public void CanGetBlobAsFilteredText()
28+
{
29+
using (var repo = new Repository(BareTestRepoPath))
30+
{
31+
var blob = repo.Lookup<Blob>("a8233120f6ad708f843d861ce2b7228ec4e3dec6");
32+
33+
var text = blob.ContentAsText(new FilteringOptions("foo.txt"));
34+
35+
ConfigurationEntry<bool> autocrlf = repo.Config.Get<bool>("core.autocrlf");
36+
37+
if (autocrlf != null && autocrlf.Value)
38+
{
39+
Assert.Equal("hey there\r\n", text);
40+
}
41+
else
42+
{
43+
Assert.Equal("hey there\n", text);
44+
}
45+
}
46+
}
47+
2548
[Theory]
2649
[InlineData("ascii", 4, "31 32 33 34")]
2750
[InlineData("utf-7", 4, "31 32 33 34")]
@@ -99,14 +122,59 @@ public void CanReadBlobStream()
99122
{
100123
var blob = repo.Lookup<Blob>("a8233120f6ad708f843d861ce2b7228ec4e3dec6");
101124

102-
using (var tr = new StreamReader(blob.ContentStream, Encoding.UTF8))
125+
using (var tr = new StreamReader(blob.ContentStream(), Encoding.UTF8))
103126
{
104127
string content = tr.ReadToEnd();
105128
Assert.Equal("hey there\n", content);
106129
}
107130
}
108131
}
109132

133+
[Fact]
134+
public void CanReadBlobFilteredStream()
135+
{
136+
using (var repo = new Repository(BareTestRepoPath))
137+
{
138+
var blob = repo.Lookup<Blob>("a8233120f6ad708f843d861ce2b7228ec4e3dec6");
139+
140+
using (var tr = new StreamReader(blob.ContentStream(new FilteringOptions("foo.txt")), Encoding.UTF8))
141+
{
142+
string content = tr.ReadToEnd();
143+
144+
ConfigurationEntry<bool> autocrlf = repo.Config.Get<bool>("core.autocrlf");
145+
146+
if (autocrlf != null && autocrlf.Value)
147+
{
148+
Assert.Equal("hey there\r\n", content);
149+
}
150+
else
151+
{
152+
Assert.Equal("hey there\n", content);
153+
}
154+
}
155+
}
156+
}
157+
158+
[Fact]
159+
public void CanReadBlobFilteredStreamOfUnmodifiedBinary()
160+
{
161+
var binaryContent = new byte[] { 0, 1, 2, 3, 4, 5 };
162+
163+
string path = CloneBareTestRepo();
164+
using (var repo = new Repository(path))
165+
{
166+
using (var stream = new MemoryStream(binaryContent))
167+
{
168+
Blob blob = repo.ObjectDatabase.CreateBlob(stream);
169+
170+
using (var filtered = blob.ContentStream(new FilteringOptions("foo.txt")))
171+
{
172+
Assert.True(StreamEquals(stream, filtered));
173+
}
174+
}
175+
}
176+
}
177+
110178
public static void CopyStream(Stream input, Stream output)
111179
{
112180
// Reused from the following Stack Overflow post with permission
@@ -120,6 +188,19 @@ public static void CopyStream(Stream input, Stream output)
120188
}
121189
}
122190

191+
public static bool StreamEquals(Stream one, Stream two)
192+
{
193+
int onebyte, twobyte;
194+
195+
while ((onebyte = one.ReadByte()) >= 0 && (twobyte = two.ReadByte()) >= 0)
196+
{
197+
if (onebyte != twobyte)
198+
return false;
199+
}
200+
201+
return true;
202+
}
203+
123204
[Fact]
124205
public void CanStageAFileGeneratedFromABlobContentStream()
125206
{
@@ -143,7 +224,7 @@ public void CanStageAFileGeneratedFromABlobContentStream()
143224

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

146-
using (Stream stream = blob.ContentStream)
227+
using (Stream stream = blob.ContentStream())
147228
using (Stream file = File.OpenWrite(Path.Combine(repo.Info.WorkingDirectory, "small.fromblob.txt")))
148229
{
149230
CopyStream(stream, file);

LibGit2Sharp/Blob.cs

+13-5
Original file line numberDiff line numberDiff line change
@@ -49,12 +49,20 @@ public virtual byte[] Content
4949
/// <summary>
5050
/// Gets the blob content in a <see cref="Stream"/>.
5151
/// </summary>
52-
public virtual Stream ContentStream
52+
public virtual Stream ContentStream()
5353
{
54-
get
55-
{
56-
return Proxy.git_blob_rawcontent_stream(repo.Handle, Id, Size);
57-
}
54+
return Proxy.git_blob_rawcontent_stream(repo.Handle, Id, Size);
55+
}
56+
57+
/// <summary>
58+
/// Gets the blob content in a <see cref="Stream"/> as it would be
59+
/// checked out to the working directory.
60+
/// <param name="filteringOptions">Parameter controlling content filtering behavior</param>
61+
/// </summary>
62+
public virtual Stream ContentStream(FilteringOptions filteringOptions)
63+
{
64+
Ensure.ArgumentNotNull(filteringOptions, "filteringOptions");
65+
return Proxy.git_blob_filtered_content_stream(repo.Handle, Id, filteringOptions.HintPath, false);
5866
}
5967
}
6068
}

LibGit2Sharp/BlobExtensions.cs

+22-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,28 @@ public static string ContentAsText(this Blob blob, Encoding encoding = null)
2222
{
2323
Ensure.ArgumentNotNull(blob, "blob");
2424

25-
using (var reader = new StreamReader(blob.ContentStream, encoding ?? Encoding.UTF8, encoding == null))
25+
using (var reader = new StreamReader(blob.ContentStream(), encoding ?? Encoding.UTF8, encoding == null))
26+
{
27+
return reader.ReadToEnd();
28+
}
29+
}
30+
31+
/// <summary>
32+
/// Gets the blob content as it would be checked out to the
33+
/// working directory, decoded with the specified encoding,
34+
/// or according to byte order marks, with UTF8 as fallback,
35+
/// if <paramref name="encoding"/> is null.
36+
/// </summary>
37+
/// <param name="blob">The blob for which the content will be returned.</param>
38+
/// <param name="filteringOptions">Parameter controlling content filtering behavior</param>
39+
/// <param name="encoding">The encoding of the text. (default: detected or UTF8)</param>
40+
/// <returns>Blob content as text.</returns>
41+
public static string ContentAsText(this Blob blob, FilteringOptions filteringOptions, Encoding encoding = null)
42+
{
43+
Ensure.ArgumentNotNull(blob, "blob");
44+
Ensure.ArgumentNotNull(filteringOptions, "filteringOptions");
45+
46+
using(var reader = new StreamReader(blob.ContentStream(filteringOptions), encoding ?? Encoding.UTF8, encoding == null))
2647
{
2748
return reader.ReadToEnd();
2849
}

LibGit2Sharp/Core/GitBuf.cs

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
using System;
2+
using System.Runtime.InteropServices;
3+
4+
namespace LibGit2Sharp.Core.Handles
5+
{
6+
[StructLayout(LayoutKind.Sequential)]
7+
internal class GitBuf : IDisposable
8+
{
9+
public IntPtr ptr;
10+
public UIntPtr asize;
11+
public UIntPtr size;
12+
13+
public void Dispose()
14+
{
15+
Proxy.git_buf_free(this);
16+
}
17+
}
18+
}

LibGit2Sharp/Core/NativeMethods.cs

+10
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,13 @@ internal static extern int git_blob_create_fromchunks(
131131
source_callback fileCallback,
132132
IntPtr data);
133133

134+
[DllImport(libgit2)]
135+
internal static extern int git_blob_filtered_content(
136+
GitBuf buf,
137+
GitObjectSafeHandle blob,
138+
[MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictFilePathMarshaler))] FilePath as_path,
139+
[MarshalAs(UnmanagedType.Bool)] bool check_for_binary_data);
140+
134141
[DllImport(libgit2)]
135142
internal static extern IntPtr git_blob_rawcontent(GitObjectSafeHandle blob);
136143

@@ -182,6 +189,9 @@ internal static extern int git_branch_upstream_name(
182189
RepositorySafeHandle repo,
183190
[MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string referenceName);
184191

192+
[DllImport(libgit2)]
193+
internal static extern void git_buf_free(GitBuf buf);
194+
185195
[DllImport(libgit2)]
186196
internal static extern int git_checkout_tree(
187197
RepositorySafeHandle repo,

LibGit2Sharp/Core/Proxy.cs

+25-1
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,20 @@ public static ObjectId git_blob_create_fromfile(RepositorySafeHandle repo, FileP
7373
}
7474
}
7575

76+
public static UnmanagedMemoryStream git_blob_filtered_content_stream(RepositorySafeHandle repo, ObjectId id, FilePath path, bool check_for_binary_data)
77+
{
78+
var buf = new GitBuf();
79+
var handle = new ObjectSafeWrapper(id, repo).ObjectPtr;
80+
81+
return new RawContentStream(handle, h =>
82+
{
83+
Ensure.ZeroResult(NativeMethods.git_blob_filtered_content(buf, h, path, check_for_binary_data));
84+
return buf.ptr;
85+
},
86+
h => (long)buf.size,
87+
new[] { buf });
88+
}
89+
7690
public static byte[] git_blob_rawcontent(RepositorySafeHandle repo, ObjectId id, int size)
7791
{
7892
using (var obj = new ObjectSafeWrapper(id, repo))
@@ -85,7 +99,8 @@ public static byte[] git_blob_rawcontent(RepositorySafeHandle repo, ObjectId id,
8599

86100
public static UnmanagedMemoryStream git_blob_rawcontent_stream(RepositorySafeHandle repo, ObjectId id, Int64 size)
87101
{
88-
return new RawContentStream(id, repo, NativeMethods.git_blob_rawcontent, size);
102+
var handle = new ObjectSafeWrapper(id, repo).ObjectPtr;
103+
return new RawContentStream(handle, NativeMethods.git_blob_rawcontent, h => size);
89104
}
90105

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

189204
#endregion
190205

206+
#region git_buf_
207+
208+
public static void git_buf_free(GitBuf buf)
209+
{
210+
NativeMethods.git_buf_free(buf);
211+
}
212+
213+
#endregion
214+
191215
#region git_checkout_
192216

193217
public static void git_checkout_tree(

LibGit2Sharp/Core/RawContentStream.cs

+48-10
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,68 @@
11
using System;
2+
using System.Collections.Generic;
23
using System.IO;
34
using LibGit2Sharp.Core.Handles;
45

56
namespace LibGit2Sharp.Core
67
{
78
internal class RawContentStream : UnmanagedMemoryStream
89
{
9-
private readonly ObjectSafeWrapper wrapper;
10+
private readonly GitObjectSafeHandle handle;
11+
private readonly ICollection<IDisposable> linkedResources;
1012

11-
internal RawContentStream(ObjectId id, RepositorySafeHandle repo,
12-
Func<GitObjectSafeHandle, IntPtr> bytePtrProvider, long length)
13-
: this(new ObjectSafeWrapper(id, repo), bytePtrProvider, length)
13+
internal unsafe RawContentStream(
14+
GitObjectSafeHandle handle,
15+
Func<GitObjectSafeHandle, IntPtr> bytePtrProvider,
16+
Func<GitObjectSafeHandle, long> sizeProvider,
17+
ICollection<IDisposable> linkedResources = null)
18+
: base((byte*)Wrap(handle, bytePtrProvider, linkedResources).ToPointer(),
19+
Wrap(handle, sizeProvider, linkedResources))
1420
{
21+
this.handle = handle;
22+
this.linkedResources = linkedResources;
1523
}
1624

17-
unsafe RawContentStream(ObjectSafeWrapper wrapper,
18-
Func<GitObjectSafeHandle, IntPtr> bytePtrProvider, long length)
19-
: base((byte*)bytePtrProvider(wrapper.ObjectPtr).ToPointer(), length)
25+
private static T Wrap<T>(
26+
GitObjectSafeHandle handle,
27+
Func<GitObjectSafeHandle, T> provider,
28+
IEnumerable<IDisposable> linkedResources)
2029
{
21-
this.wrapper = wrapper;
30+
T value;
31+
32+
try
33+
{
34+
value = provider(handle);
35+
}
36+
catch
37+
{
38+
Dispose(handle, linkedResources);
39+
throw;
40+
}
41+
42+
return value;
43+
}
44+
45+
private static void Dispose(
46+
GitObjectSafeHandle handle,
47+
IEnumerable<IDisposable> linkedResources)
48+
{
49+
handle.SafeDispose();
50+
51+
if (linkedResources == null)
52+
{
53+
return;
54+
}
55+
56+
foreach (IDisposable linkedResource in linkedResources)
57+
{
58+
linkedResource.Dispose();
59+
}
2260
}
2361

2462
protected override void Dispose(bool disposing)
2563
{
2664
base.Dispose(disposing);
27-
wrapper.SafeDispose();
65+
Dispose(handle, linkedResources);
2866
}
29-
}
67+
}
3068
}

LibGit2Sharp/FilteringOptions.cs

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
using LibGit2Sharp.Core;
2+
3+
namespace LibGit2Sharp
4+
{
5+
/// <summary>
6+
/// Allows callers to specify how blob content filters will be applied.
7+
/// </summary>
8+
public sealed class FilteringOptions
9+
{
10+
/// <summary>
11+
/// Initializes a new instance of the <see cref="FilteringOptions"/> class.
12+
/// </summary>
13+
/// <param name="hintPath">The path that a file would be checked out as</param>
14+
public FilteringOptions(string hintPath)
15+
{
16+
Ensure.ArgumentNotNull(hintPath, "hintPath");
17+
18+
this.HintPath = hintPath;
19+
}
20+
21+
/// <summary>
22+
/// The path to "hint" to the filters will be used to apply
23+
/// attributes.
24+
/// </summary>
25+
public string HintPath { get; private set; }
26+
}
27+
}

LibGit2Sharp/LibGit2Sharp.csproj

+2
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@
7373
<Compile Include="CompareOptions.cs" />
7474
<Compile Include="Core\EncodingMarshaler.cs" />
7575
<Compile Include="PushOptions.cs" />
76+
<Compile Include="Core\GitBuf.cs" />
77+
<Compile Include="FilteringOptions.cs" />
7678
<Compile Include="UnbornBranchException.cs" />
7779
<Compile Include="LockedFileException.cs" />
7880
<Compile Include="Core\GitRepositoryInitOptions.cs" />

0 commit comments

Comments
 (0)