Skip to content

Commit

Permalink
Merge branch 'newline-fix' into HEAD
Browse files Browse the repository at this point in the history
  • Loading branch information
vdye committed Jan 14, 2025
2 parents 21fda9f + 786ab03 commit 4c32c09
Show file tree
Hide file tree
Showing 5 changed files with 267 additions and 4 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,8 @@ jobs:
- name: Set up signing/notarization infrastructure
env:
A1: ${{ secrets.APPLICATION_CERTIFICATE_BASE64 }}
A2: ${{ secrets.APPLICATION_CERTIFICATE_PASSWORD }}
A1: ${{ secrets.GATEWATCHER_DEVELOPER_ID_CERT }}
A2: ${{ secrets.GATEWATCHER_DEVELOPER_ID_PASSWORD }}
I1: ${{ secrets.INSTALLER_CERTIFICATE_BASE64 }}
I2: ${{ secrets.INSTALLER_CERTIFICATE_PASSWORD }}
N1: ${{ secrets.APPLE_TEAM_ID }}
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
2.6.0.0
2.6.1.0
193 changes: 193 additions & 0 deletions src/shared/Core.Tests/GitStreamReaderTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
using System.IO;
using System.Text;
using System.Threading.Tasks;
using Xunit;

namespace GitCredentialManager.Tests;

public class GitStreamReaderTests
{
#region ReadLineAsync

[Fact]
public async Task GitStreamReader_ReadLineAsync_LF()
{
// hello\n
// world\n

byte[] buffer = Encoding.UTF8.GetBytes("hello\nworld\n");
using var stream = new MemoryStream(buffer);
var reader = new GitStreamReader(stream, Encoding.UTF8);

string actual1 = await reader.ReadLineAsync();
string actual2 = await reader.ReadLineAsync();
string actual3 = await reader.ReadLineAsync();

Assert.Equal("hello", actual1);
Assert.Equal("world", actual2);
Assert.Null(actual3);
}

[Fact]
public async Task GitStreamReader_ReadLineAsync_CR()
{
// hello\rworld\r

byte[] buffer = Encoding.UTF8.GetBytes("hello\rworld\r");
using var stream = new MemoryStream(buffer);
var reader = new GitStreamReader(stream, Encoding.UTF8);

string actual1 = await reader.ReadLineAsync();
string actual2 = await reader.ReadLineAsync();

Assert.Equal("hello\rworld\r", actual1);
Assert.Null(actual2);
}

[Fact]
public async Task GitStreamReader_ReadLineAsync_CRLF()
{
// hello\r\n
// world\r\n

byte[] buffer = Encoding.UTF8.GetBytes("hello\r\nworld\r\n");
using var stream = new MemoryStream(buffer);
var reader = new GitStreamReader(stream, Encoding.UTF8);

string actual1 = await reader.ReadLineAsync();
string actual2 = await reader.ReadLineAsync();
string actual3 = await reader.ReadLineAsync();

Assert.Equal("hello", actual1);
Assert.Equal("world", actual2);
Assert.Null(actual3);
}

[Fact]
public async Task GitStreamReader_ReadLineAsync_Mixed()
{
// hello\r\n
// world\rthis\n
// is\n
// a\n
// \rmixed\rnewline\r\n
// \n
// string\n

byte[] buffer = Encoding.UTF8.GetBytes("hello\r\nworld\rthis\nis\na\n\rmixed\rnewline\r\n\nstring\n");
using var stream = new MemoryStream(buffer);
var reader = new GitStreamReader(stream, Encoding.UTF8);

string actual1 = await reader.ReadLineAsync();
string actual2 = await reader.ReadLineAsync();
string actual3 = await reader.ReadLineAsync();
string actual4 = await reader.ReadLineAsync();
string actual5 = await reader.ReadLineAsync();
string actual6 = await reader.ReadLineAsync();
string actual7 = await reader.ReadLineAsync();
string actual8 = await reader.ReadLineAsync();

Assert.Equal("hello", actual1);
Assert.Equal("world\rthis", actual2);
Assert.Equal("is", actual3);
Assert.Equal("a", actual4);
Assert.Equal("\rmixed\rnewline", actual5);
Assert.Equal("", actual6);
Assert.Equal("string", actual7);
Assert.Null(actual8);
}

#endregion

#region ReadLine

[Fact]
public void GitStreamReader_ReadLine_LF()
{
// hello\n
// world\n

byte[] buffer = Encoding.UTF8.GetBytes("hello\nworld\n");
using var stream = new MemoryStream(buffer);
var reader = new GitStreamReader(stream, Encoding.UTF8);

string actual1 = reader.ReadLine();
string actual2 = reader.ReadLine();
string actual3 = reader.ReadLine();

Assert.Equal("hello", actual1);
Assert.Equal("world", actual2);
Assert.Null(actual3);
}

[Fact]
public void GitStreamReader_ReadLine_CR()
{
// hello\rworld\r

byte[] buffer = Encoding.UTF8.GetBytes("hello\rworld\r");
using var stream = new MemoryStream(buffer);
var reader = new GitStreamReader(stream, Encoding.UTF8);

string actual1 = reader.ReadLine();
string actual2 = reader.ReadLine();

Assert.Equal("hello\rworld\r", actual1);
Assert.Null(actual2);
}

[Fact]
public void GitStreamReader_ReadLine_CRLF()
{
// hello\r\n
// world\r\n

byte[] buffer = Encoding.UTF8.GetBytes("hello\r\nworld\r\n");
using var stream = new MemoryStream(buffer);
var reader = new GitStreamReader(stream, Encoding.UTF8);

string actual1 = reader.ReadLine();
string actual2 = reader.ReadLine();
string actual3 = reader.ReadLine();

Assert.Equal("hello", actual1);
Assert.Equal("world", actual2);
Assert.Null(actual3);
}

[Fact]
public void GitStreamReader_ReadLine_Mixed()
{
// hello\r\n
// world\rthis\n
// is\n
// a\n
// \rmixed\rnewline\r\n
// \n
// string\n

byte[] buffer = Encoding.UTF8.GetBytes("hello\r\nworld\rthis\nis\na\n\rmixed\rnewline\r\n\nstring\n");
using var stream = new MemoryStream(buffer);
var reader = new GitStreamReader(stream, Encoding.UTF8);

string actual1 = reader.ReadLine();
string actual2 = reader.ReadLine();
string actual3 = reader.ReadLine();
string actual4 = reader.ReadLine();
string actual5 = reader.ReadLine();
string actual6 = reader.ReadLine();
string actual7 = reader.ReadLine();
string actual8 = reader.ReadLine();

Assert.Equal("hello", actual1);
Assert.Equal("world\rthis", actual2);
Assert.Equal("is", actual3);
Assert.Equal("a", actual4);
Assert.Equal("\rmixed\rnewline", actual5);
Assert.Equal("", actual6);
Assert.Equal("string", actual7);
Assert.Null(actual8);
}

#endregion
}
70 changes: 70 additions & 0 deletions src/shared/Core/GitStreamReader.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace GitCredentialManager;

/// <summary>
/// StreamReader that does NOT consider a lone carriage-return as a new-line character,
/// only a line-feed or carriage-return immediately followed by a line-feed.
/// <para/>
/// The only major operating system that uses a lone carriage-return as a new-line character
/// is the classic Macintosh OS (before OS X), which is not supported by Git.
/// </summary>
public class GitStreamReader : StreamReader
{
public GitStreamReader(Stream stream, Encoding encoding) : base(stream, encoding) { }

public override string ReadLine()
{
#if NETFRAMEWORK
return ReadLineAsync().ConfigureAwait(false).GetAwaiter().GetResult();
#else
return ReadLineAsync(CancellationToken.None).ConfigureAwait(false).GetAwaiter().GetResult();
#endif
}

#if NETFRAMEWORK
public override async Task<string> ReadLineAsync()
#else
public override async ValueTask<string> ReadLineAsync(CancellationToken cancellationToken)
#endif
{
int nr;
var sb = new StringBuilder();
var buffer = new char[1];
bool lastWasCR = false;

while ((nr = await base.ReadAsync(buffer, 0, 1).ConfigureAwait(false)) > 0)
{
char c = buffer[0];

// Only treat a line-feed as a new-line character.
// Carriage-returns alone are NOT considered new-line characters.
if (c == '\n')
{
if (lastWasCR)
{
// If the last character was a carriage-return we should remove it from the string builder
// since together with this line-feed it is considered a new-line character.
sb.Length--;
}

// We have a new-line character, so we should stop reading.
break;
}

lastWasCR = c == '\r';

sb.Append(c);
}

if (sb.Length == 0 && nr == 0)
{
return null;
}

return sb.ToString();
}
}
2 changes: 1 addition & 1 deletion src/shared/Core/StandardStreams.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public TextReader In
{
if (_stdIn == null)
{
_stdIn = new StreamReader(Console.OpenStandardInput(), EncodingEx.UTF8NoBom);
_stdIn = new GitStreamReader(Console.OpenStandardInput(), EncodingEx.UTF8NoBom);
}

return _stdIn;
Expand Down

0 comments on commit 4c32c09

Please sign in to comment.