Skip to content

Commit c5eb024

Browse files
authored
feature: allow partial stage/unstage/discard for non-UTF8 text in diff view (#2260)
Current implementation fails on partial stage/unstage/discard operations for non-UTF8 text because of applying a patch made with broken (replaced) text. This modification allows these operation by preserving the original raw bytes from the output of `git diff`, and use it to create patch file.
1 parent 0d5185b commit c5eb024

2 files changed

Lines changed: 41 additions & 36 deletions

File tree

src/Commands/Diff.cs

Lines changed: 22 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -45,24 +45,23 @@ public Diff(string repo, Models.DiffOption opt, int unified, bool ignoreWhitespa
4545
using var proc = new Process();
4646
proc.StartInfo = CreateGitStartInfo(true);
4747
proc.Start();
48-
49-
var text = await proc.StandardOutput.ReadToEndAsync().ConfigureAwait(false);
50-
48+
using var ms = new System.IO.MemoryStream();
49+
await proc.StandardOutput.BaseStream.CopyToAsync(ms, CancellationToken).ConfigureAwait(false);
50+
var bytes = ms.ToArray();
5151
var start = 0;
52-
var end = text.IndexOf('\n', start);
53-
while (end > 0)
52+
while (start < bytes.Length)
5453
{
55-
var line = text[start..end];
56-
ParseLine(line);
57-
58-
start = end + 1;
59-
end = text.IndexOf('\n', start);
54+
var end = Array.IndexOf(bytes, (byte)'\n', start);
55+
if (end < 0)
56+
end = bytes.Length;
57+
var next = end + 1;
58+
if (start <= end - 1 && bytes[end - 1] == '\r')
59+
end--;
60+
if (!_result.IsBinary)
61+
ParseLine(bytes[start..end]);
62+
start = next;
6063
}
61-
62-
if (start < text.Length)
63-
ParseLine(text[start..]);
64-
65-
await proc.WaitForExitAsync().ConfigureAwait(false);
64+
await proc.WaitForExitAsync(CancellationToken).ConfigureAwait(false);
6665
}
6766
catch
6867
{
@@ -82,10 +81,9 @@ public Diff(string repo, Models.DiffOption opt, int unified, bool ignoreWhitespa
8281
return _result;
8382
}
8483

85-
private void ParseLine(string line)
84+
private void ParseLine(byte[] lineBytes)
8685
{
87-
if (_result.IsBinary)
88-
return;
86+
var line = Encoding.UTF8.GetString(lineBytes);
8987

9088
if (line.StartsWith("old mode ", StringComparison.Ordinal))
9189
{
@@ -168,7 +166,7 @@ private void ParseLine(string line)
168166

169167
_oldLine = int.Parse(match.Groups[1].Value);
170168
_newLine = int.Parse(match.Groups[2].Value);
171-
_last = new Models.TextDiffLine(Models.TextDiffLineType.Indicator, line, 0, 0);
169+
_last = new Models.TextDiffLine(Models.TextDiffLineType.Indicator, lineBytes, 0, 0);
172170
_result.TextDiff.Lines.Add(_last);
173171
}
174172
}
@@ -177,7 +175,7 @@ private void ParseLine(string line)
177175
if (line.Length == 0)
178176
{
179177
ProcessInlineHighlights();
180-
_last = new Models.TextDiffLine(Models.TextDiffLineType.Normal, "", _oldLine, _newLine);
178+
_last = new Models.TextDiffLine(Models.TextDiffLineType.Normal, Array.Empty<byte>(), _oldLine, _newLine);
181179
_result.TextDiff.Lines.Add(_last);
182180
_oldLine++;
183181
_newLine++;
@@ -195,7 +193,7 @@ private void ParseLine(string line)
195193
}
196194

197195
_result.TextDiff.DeletedLines++;
198-
_last = new Models.TextDiffLine(Models.TextDiffLineType.Deleted, line.Substring(1), _oldLine, 0);
196+
_last = new Models.TextDiffLine(Models.TextDiffLineType.Deleted, lineBytes[1..], _oldLine, 0);
199197
_deleted.Add(_last);
200198
_oldLine++;
201199
}
@@ -209,7 +207,7 @@ private void ParseLine(string line)
209207
}
210208

211209
_result.TextDiff.AddedLines++;
212-
_last = new Models.TextDiffLine(Models.TextDiffLineType.Added, line.Substring(1), 0, _newLine);
210+
_last = new Models.TextDiffLine(Models.TextDiffLineType.Added, lineBytes[1..], 0, _newLine);
213211
_added.Add(_last);
214212
_newLine++;
215213
}
@@ -221,7 +219,7 @@ private void ParseLine(string line)
221219
{
222220
_oldLine = int.Parse(match.Groups[1].Value);
223221
_newLine = int.Parse(match.Groups[2].Value);
224-
_last = new Models.TextDiffLine(Models.TextDiffLineType.Indicator, line, 0, 0);
222+
_last = new Models.TextDiffLine(Models.TextDiffLineType.Indicator, lineBytes, 0, 0);
225223
_result.TextDiff.Lines.Add(_last);
226224
}
227225
else
@@ -233,7 +231,7 @@ private void ParseLine(string line)
233231
return;
234232
}
235233

236-
_last = new Models.TextDiffLine(Models.TextDiffLineType.Normal, line.Substring(1), _oldLine, _newLine);
234+
_last = new Models.TextDiffLine(Models.TextDiffLineType.Normal, lineBytes[1..], _oldLine, _newLine);
237235
_result.TextDiff.Lines.Add(_last);
238236
_oldLine++;
239237
_newLine++;

src/Models/DiffResult.cs

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ public class TextRange(int p, int n)
2323
public class TextDiffLine
2424
{
2525
public TextDiffLineType Type { get; set; } = TextDiffLineType.None;
26+
public byte[] RawContent { get; set; } = [];
2627
public string Content { get; set; } = "";
2728
public int OldLineNumber { get; set; } = 0;
2829
public int NewLineNumber { get; set; } = 0;
@@ -33,10 +34,13 @@ public class TextDiffLine
3334
public string NewLine => NewLineNumber == 0 ? string.Empty : NewLineNumber.ToString();
3435

3536
public TextDiffLine() { }
36-
public TextDiffLine(TextDiffLineType type, string content, int oldLine, int newLine)
37+
public TextDiffLine(TextDiffLineType type, byte[] rawContent, int oldLine, int newLine)
3738
{
39+
if (rawContent == null)
40+
throw new System.ArgumentNullException(nameof(rawContent));
3841
Type = type;
39-
Content = content;
42+
Content = System.Text.Encoding.UTF8.GetString(rawContent);
43+
RawContent = rawContent;
4044
OldLineNumber = oldLine;
4145
NewLineNumber = newLine;
4246
}
@@ -158,7 +162,7 @@ public void GeneratePatchFromSelection(Change change, string fileTreeGuid, TextD
158162
writer.WriteLine($"+++ b/{change.Path}");
159163

160164
// If last line of selection is a change. Find one more line.
161-
string tail = null;
165+
TextDiffLine tail = null;
162166
if (selection.EndLine < Lines.Count)
163167
{
164168
var lastLine = Lines[selection.EndLine - 1];
@@ -173,7 +177,7 @@ public void GeneratePatchFromSelection(Change change, string fileTreeGuid, TextD
173177
(revert && line.Type == TextDiffLineType.Added) ||
174178
(!revert && line.Type == TextDiffLineType.Deleted))
175179
{
176-
tail = line.Content;
180+
tail = line;
177181
break;
178182
}
179183
}
@@ -256,8 +260,8 @@ public void GeneratePatchFromSelection(Change change, string fileTreeGuid, TextD
256260
}
257261
}
258262

259-
if (!string.IsNullOrEmpty(tail))
260-
writer.WriteLine($" {tail}");
263+
if (tail != null)
264+
WriteLine(writer, ' ', tail);
261265
writer.Flush();
262266
}
263267

@@ -273,7 +277,7 @@ public void GeneratePatchFromSelectionSingleSide(Change change, string fileTreeG
273277
writer.WriteLine($"+++ b/{change.Path}");
274278

275279
// If last line of selection is a change. Find one more line.
276-
string tail = null;
280+
TextDiffLine tail = null;
277281
if (selection.EndLine < Lines.Count)
278282
{
279283
var lastLine = Lines[selection.EndLine - 1];
@@ -288,15 +292,15 @@ public void GeneratePatchFromSelectionSingleSide(Change change, string fileTreeG
288292
{
289293
if (line.Type == TextDiffLineType.Normal || line.Type == TextDiffLineType.Added)
290294
{
291-
tail = line.Content;
295+
tail = line;
292296
break;
293297
}
294298
}
295299
else
296300
{
297301
if (line.Type == TextDiffLineType.Normal || line.Type == TextDiffLineType.Deleted)
298302
{
299-
tail = line.Content;
303+
tail = line;
300304
break;
301305
}
302306
}
@@ -408,8 +412,8 @@ public void GeneratePatchFromSelectionSingleSide(Change change, string fileTreeG
408412
}
409413
}
410414

411-
if (!string.IsNullOrEmpty(tail))
412-
writer.WriteLine($" {tail}");
415+
if (tail != null)
416+
WriteLine(writer, ' ', tail);
413417
writer.Flush();
414418
}
415419

@@ -564,7 +568,10 @@ private bool ProcessIndicatorForPatchSingleSide(StreamWriter writer, TextDiffLin
564568

565569
private static void WriteLine(StreamWriter writer, char prefix, TextDiffLine line)
566570
{
567-
writer.WriteLine($"{prefix}{line.Content}");
571+
writer.Write($"{prefix}");
572+
writer.Flush();
573+
writer.BaseStream.Write(line.RawContent); // write original bytes
574+
writer.WriteLine();
568575

569576
if (line.NoNewLineEndOfFile)
570577
writer.WriteLine("\\ No newline at end of file");

0 commit comments

Comments
 (0)