Skip to content

Commit 4c32c09

Browse files
committed
Merge branch 'newline-fix' into HEAD
2 parents 21fda9f + 786ab03 commit 4c32c09

File tree

5 files changed

+267
-4
lines changed

5 files changed

+267
-4
lines changed

.github/workflows/release.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,8 @@ jobs:
5757
5858
- name: Set up signing/notarization infrastructure
5959
env:
60-
A1: ${{ secrets.APPLICATION_CERTIFICATE_BASE64 }}
61-
A2: ${{ secrets.APPLICATION_CERTIFICATE_PASSWORD }}
60+
A1: ${{ secrets.GATEWATCHER_DEVELOPER_ID_CERT }}
61+
A2: ${{ secrets.GATEWATCHER_DEVELOPER_ID_PASSWORD }}
6262
I1: ${{ secrets.INSTALLER_CERTIFICATE_BASE64 }}
6363
I2: ${{ secrets.INSTALLER_CERTIFICATE_PASSWORD }}
6464
N1: ${{ secrets.APPLE_TEAM_ID }}

VERSION

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
2.6.0.0
1+
2.6.1.0
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
using System.IO;
2+
using System.Text;
3+
using System.Threading.Tasks;
4+
using Xunit;
5+
6+
namespace GitCredentialManager.Tests;
7+
8+
public class GitStreamReaderTests
9+
{
10+
#region ReadLineAsync
11+
12+
[Fact]
13+
public async Task GitStreamReader_ReadLineAsync_LF()
14+
{
15+
// hello\n
16+
// world\n
17+
18+
byte[] buffer = Encoding.UTF8.GetBytes("hello\nworld\n");
19+
using var stream = new MemoryStream(buffer);
20+
var reader = new GitStreamReader(stream, Encoding.UTF8);
21+
22+
string actual1 = await reader.ReadLineAsync();
23+
string actual2 = await reader.ReadLineAsync();
24+
string actual3 = await reader.ReadLineAsync();
25+
26+
Assert.Equal("hello", actual1);
27+
Assert.Equal("world", actual2);
28+
Assert.Null(actual3);
29+
}
30+
31+
[Fact]
32+
public async Task GitStreamReader_ReadLineAsync_CR()
33+
{
34+
// hello\rworld\r
35+
36+
byte[] buffer = Encoding.UTF8.GetBytes("hello\rworld\r");
37+
using var stream = new MemoryStream(buffer);
38+
var reader = new GitStreamReader(stream, Encoding.UTF8);
39+
40+
string actual1 = await reader.ReadLineAsync();
41+
string actual2 = await reader.ReadLineAsync();
42+
43+
Assert.Equal("hello\rworld\r", actual1);
44+
Assert.Null(actual2);
45+
}
46+
47+
[Fact]
48+
public async Task GitStreamReader_ReadLineAsync_CRLF()
49+
{
50+
// hello\r\n
51+
// world\r\n
52+
53+
byte[] buffer = Encoding.UTF8.GetBytes("hello\r\nworld\r\n");
54+
using var stream = new MemoryStream(buffer);
55+
var reader = new GitStreamReader(stream, Encoding.UTF8);
56+
57+
string actual1 = await reader.ReadLineAsync();
58+
string actual2 = await reader.ReadLineAsync();
59+
string actual3 = await reader.ReadLineAsync();
60+
61+
Assert.Equal("hello", actual1);
62+
Assert.Equal("world", actual2);
63+
Assert.Null(actual3);
64+
}
65+
66+
[Fact]
67+
public async Task GitStreamReader_ReadLineAsync_Mixed()
68+
{
69+
// hello\r\n
70+
// world\rthis\n
71+
// is\n
72+
// a\n
73+
// \rmixed\rnewline\r\n
74+
// \n
75+
// string\n
76+
77+
byte[] buffer = Encoding.UTF8.GetBytes("hello\r\nworld\rthis\nis\na\n\rmixed\rnewline\r\n\nstring\n");
78+
using var stream = new MemoryStream(buffer);
79+
var reader = new GitStreamReader(stream, Encoding.UTF8);
80+
81+
string actual1 = await reader.ReadLineAsync();
82+
string actual2 = await reader.ReadLineAsync();
83+
string actual3 = await reader.ReadLineAsync();
84+
string actual4 = await reader.ReadLineAsync();
85+
string actual5 = await reader.ReadLineAsync();
86+
string actual6 = await reader.ReadLineAsync();
87+
string actual7 = await reader.ReadLineAsync();
88+
string actual8 = await reader.ReadLineAsync();
89+
90+
Assert.Equal("hello", actual1);
91+
Assert.Equal("world\rthis", actual2);
92+
Assert.Equal("is", actual3);
93+
Assert.Equal("a", actual4);
94+
Assert.Equal("\rmixed\rnewline", actual5);
95+
Assert.Equal("", actual6);
96+
Assert.Equal("string", actual7);
97+
Assert.Null(actual8);
98+
}
99+
100+
#endregion
101+
102+
#region ReadLine
103+
104+
[Fact]
105+
public void GitStreamReader_ReadLine_LF()
106+
{
107+
// hello\n
108+
// world\n
109+
110+
byte[] buffer = Encoding.UTF8.GetBytes("hello\nworld\n");
111+
using var stream = new MemoryStream(buffer);
112+
var reader = new GitStreamReader(stream, Encoding.UTF8);
113+
114+
string actual1 = reader.ReadLine();
115+
string actual2 = reader.ReadLine();
116+
string actual3 = reader.ReadLine();
117+
118+
Assert.Equal("hello", actual1);
119+
Assert.Equal("world", actual2);
120+
Assert.Null(actual3);
121+
}
122+
123+
[Fact]
124+
public void GitStreamReader_ReadLine_CR()
125+
{
126+
// hello\rworld\r
127+
128+
byte[] buffer = Encoding.UTF8.GetBytes("hello\rworld\r");
129+
using var stream = new MemoryStream(buffer);
130+
var reader = new GitStreamReader(stream, Encoding.UTF8);
131+
132+
string actual1 = reader.ReadLine();
133+
string actual2 = reader.ReadLine();
134+
135+
Assert.Equal("hello\rworld\r", actual1);
136+
Assert.Null(actual2);
137+
}
138+
139+
[Fact]
140+
public void GitStreamReader_ReadLine_CRLF()
141+
{
142+
// hello\r\n
143+
// world\r\n
144+
145+
byte[] buffer = Encoding.UTF8.GetBytes("hello\r\nworld\r\n");
146+
using var stream = new MemoryStream(buffer);
147+
var reader = new GitStreamReader(stream, Encoding.UTF8);
148+
149+
string actual1 = reader.ReadLine();
150+
string actual2 = reader.ReadLine();
151+
string actual3 = reader.ReadLine();
152+
153+
Assert.Equal("hello", actual1);
154+
Assert.Equal("world", actual2);
155+
Assert.Null(actual3);
156+
}
157+
158+
[Fact]
159+
public void GitStreamReader_ReadLine_Mixed()
160+
{
161+
// hello\r\n
162+
// world\rthis\n
163+
// is\n
164+
// a\n
165+
// \rmixed\rnewline\r\n
166+
// \n
167+
// string\n
168+
169+
byte[] buffer = Encoding.UTF8.GetBytes("hello\r\nworld\rthis\nis\na\n\rmixed\rnewline\r\n\nstring\n");
170+
using var stream = new MemoryStream(buffer);
171+
var reader = new GitStreamReader(stream, Encoding.UTF8);
172+
173+
string actual1 = reader.ReadLine();
174+
string actual2 = reader.ReadLine();
175+
string actual3 = reader.ReadLine();
176+
string actual4 = reader.ReadLine();
177+
string actual5 = reader.ReadLine();
178+
string actual6 = reader.ReadLine();
179+
string actual7 = reader.ReadLine();
180+
string actual8 = reader.ReadLine();
181+
182+
Assert.Equal("hello", actual1);
183+
Assert.Equal("world\rthis", actual2);
184+
Assert.Equal("is", actual3);
185+
Assert.Equal("a", actual4);
186+
Assert.Equal("\rmixed\rnewline", actual5);
187+
Assert.Equal("", actual6);
188+
Assert.Equal("string", actual7);
189+
Assert.Null(actual8);
190+
}
191+
192+
#endregion
193+
}

src/shared/Core/GitStreamReader.cs

+70
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
using System.IO;
2+
using System.Text;
3+
using System.Threading;
4+
using System.Threading.Tasks;
5+
6+
namespace GitCredentialManager;
7+
8+
/// <summary>
9+
/// StreamReader that does NOT consider a lone carriage-return as a new-line character,
10+
/// only a line-feed or carriage-return immediately followed by a line-feed.
11+
/// <para/>
12+
/// The only major operating system that uses a lone carriage-return as a new-line character
13+
/// is the classic Macintosh OS (before OS X), which is not supported by Git.
14+
/// </summary>
15+
public class GitStreamReader : StreamReader
16+
{
17+
public GitStreamReader(Stream stream, Encoding encoding) : base(stream, encoding) { }
18+
19+
public override string ReadLine()
20+
{
21+
#if NETFRAMEWORK
22+
return ReadLineAsync().ConfigureAwait(false).GetAwaiter().GetResult();
23+
#else
24+
return ReadLineAsync(CancellationToken.None).ConfigureAwait(false).GetAwaiter().GetResult();
25+
#endif
26+
}
27+
28+
#if NETFRAMEWORK
29+
public override async Task<string> ReadLineAsync()
30+
#else
31+
public override async ValueTask<string> ReadLineAsync(CancellationToken cancellationToken)
32+
#endif
33+
{
34+
int nr;
35+
var sb = new StringBuilder();
36+
var buffer = new char[1];
37+
bool lastWasCR = false;
38+
39+
while ((nr = await base.ReadAsync(buffer, 0, 1).ConfigureAwait(false)) > 0)
40+
{
41+
char c = buffer[0];
42+
43+
// Only treat a line-feed as a new-line character.
44+
// Carriage-returns alone are NOT considered new-line characters.
45+
if (c == '\n')
46+
{
47+
if (lastWasCR)
48+
{
49+
// If the last character was a carriage-return we should remove it from the string builder
50+
// since together with this line-feed it is considered a new-line character.
51+
sb.Length--;
52+
}
53+
54+
// We have a new-line character, so we should stop reading.
55+
break;
56+
}
57+
58+
lastWasCR = c == '\r';
59+
60+
sb.Append(c);
61+
}
62+
63+
if (sb.Length == 0 && nr == 0)
64+
{
65+
return null;
66+
}
67+
68+
return sb.ToString();
69+
}
70+
}

src/shared/Core/StandardStreams.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ public TextReader In
3939
{
4040
if (_stdIn == null)
4141
{
42-
_stdIn = new StreamReader(Console.OpenStandardInput(), EncodingEx.UTF8NoBom);
42+
_stdIn = new GitStreamReader(Console.OpenStandardInput(), EncodingEx.UTF8NoBom);
4343
}
4444

4545
return _stdIn;

0 commit comments

Comments
 (0)