Skip to content

Commit ef9c066

Browse files
committed
fix: correct patch generation when discarding partial changes of staged new files
When a new file was already staged and the user tried to discard selected lines from the unstaged changes list, the generated patch was malformed due to incorrect index header format, wrong hunk line numbers, and unconditional "No newline at end of file" marker.
1 parent 7a753e7 commit ef9c066

File tree

2 files changed

+39
-53
lines changed

2 files changed

+39
-53
lines changed

src/Models/DiffResult.cs

Lines changed: 34 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -95,53 +95,45 @@ public TextDiffSelection MakeSelection(int startLine, int endLine, bool isCombin
9595
return rs;
9696
}
9797

98-
public void GenerateNewPatchFromSelection(Change change, string fileBlobGuid, TextDiffSelection selection, bool revert, string output)
98+
public void GenerateNewPatchFromSelection(Change change, string fileBlobGuid, TextDiffSelection selection, string output)
9999
{
100100
var isTracked = !string.IsNullOrEmpty(fileBlobGuid);
101101
var fileGuid = isTracked ? fileBlobGuid : "00000000";
102102

103+
// Collect selected added lines first to check if there's anything to write
104+
var selectedAddedLines = new List<TextDiffLine>();
105+
for (int i = 0; i < Lines.Count; i++)
106+
{
107+
var line = Lines[i];
108+
if (line.Type != TextDiffLineType.Added)
109+
continue;
110+
111+
var lineIndex = i + 1; // 1-based line number
112+
if (lineIndex >= selection.StartLine && lineIndex <= selection.EndLine)
113+
selectedAddedLines.Add(line);
114+
}
115+
116+
if (selectedAddedLines.Count == 0)
117+
return;
118+
103119
using var writer = new StreamWriter(output);
104120
writer.NewLine = "\n";
105121
writer.WriteLine($"diff --git a/{change.Path} b/{change.Path}");
106-
if (!revert && !isTracked)
122+
if (!isTracked)
107123
writer.WriteLine("new file mode 100644");
108-
writer.WriteLine($"index 00000000...{fileGuid}");
109-
writer.WriteLine($"--- {(revert || isTracked ? $"a/{change.Path}" : "/dev/null")}");
124+
writer.WriteLine($"index 0000000..{fileGuid}");
125+
writer.WriteLine($"--- {(isTracked ? $"a/{change.Path}" : "/dev/null")}");
110126
writer.WriteLine($"+++ b/{change.Path}");
111127

112-
var additions = selection.EndLine - selection.StartLine;
113-
if (selection.StartLine != 1)
114-
additions++;
115-
116-
if (revert)
128+
writer.WriteLine($"@@ -0,0 +1,{selectedAddedLines.Count} @@");
129+
foreach (var line in selectedAddedLines)
117130
{
118-
var totalLines = Lines.Count - 1;
119-
writer.WriteLine($"@@ -0,{totalLines - additions} +0,{totalLines} @@");
120-
for (int i = 1; i <= totalLines; i++)
121-
{
122-
var line = Lines[i];
123-
if (line.Type != TextDiffLineType.Added)
124-
continue;
125-
126-
if (i >= selection.StartLine - 1 && i < selection.EndLine)
127-
writer.WriteLine($"+{line.Content}");
128-
else
129-
writer.WriteLine($" {line.Content}");
130-
}
131-
}
132-
else
133-
{
134-
writer.WriteLine($"@@ -0,0 +0,{additions} @@");
135-
for (int i = selection.StartLine - 1; i < selection.EndLine; i++)
136-
{
137-
var line = Lines[i];
138-
if (line.Type != TextDiffLineType.Added)
139-
continue;
140-
writer.WriteLine($"+{line.Content}");
141-
}
131+
writer.WriteLine($"+{line.Content}");
142132
}
143133

144-
writer.WriteLine("\\ No newline at end of file");
134+
if (selectedAddedLines[^1].NoNewLineEndOfFile)
135+
writer.WriteLine("\\ No newline at end of file");
136+
145137
writer.Flush();
146138
}
147139

@@ -152,7 +144,7 @@ public void GeneratePatchFromSelection(Change change, string fileTreeGuid, TextD
152144
using var writer = new StreamWriter(output);
153145
writer.NewLine = "\n";
154146
writer.WriteLine($"diff --git a/{change.Path} b/{change.Path}");
155-
writer.WriteLine($"index 00000000...{fileTreeGuid} 100644");
147+
writer.WriteLine($"index 0000000..{fileTreeGuid} 100644");
156148
writer.WriteLine($"--- a/{orgFile}");
157149
writer.WriteLine($"+++ b/{change.Path}");
158150

@@ -255,7 +247,9 @@ public void GeneratePatchFromSelection(Change change, string fileTreeGuid, TextD
255247
}
256248
}
257249

258-
writer.WriteLine($" {tail}");
250+
if (tail != null)
251+
writer.WriteLine($" {tail}");
252+
259253
writer.Flush();
260254
}
261255

@@ -266,7 +260,7 @@ public void GeneratePatchFromSelectionSingleSide(Change change, string fileTreeG
266260
using var writer = new StreamWriter(output);
267261
writer.NewLine = "\n";
268262
writer.WriteLine($"diff --git a/{change.Path} b/{change.Path}");
269-
writer.WriteLine($"index 00000000...{fileTreeGuid} 100644");
263+
writer.WriteLine($"index 0000000..{fileTreeGuid} 100644");
270264
writer.WriteLine($"--- a/{orgFile}");
271265
writer.WriteLine($"+++ b/{change.Path}");
272266

@@ -406,7 +400,9 @@ public void GeneratePatchFromSelectionSingleSide(Change change, string fileTreeG
406400
}
407401
}
408402

409-
writer.WriteLine($" {tail}");
403+
if (tail != null)
404+
writer.WriteLine($" {tail}");
405+
410406
writer.Flush();
411407
}
412408

src/Views/TextDiffView.axaml.cs

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1495,7 +1495,7 @@ private async void OnStageChunk(object _1, RoutedEventArgs _2)
14951495
var tmpFile = Path.GetTempFileName();
14961496
if (change.WorkTree == Models.ChangeState.Untracked)
14971497
{
1498-
diff.GenerateNewPatchFromSelection(change, null, selection, false, tmpFile);
1498+
diff.GenerateNewPatchFromSelection(change, null, selection, tmpFile);
14991499
}
15001500
else if (chunk.Combined)
15011501
{
@@ -1532,9 +1532,7 @@ private async void OnUnstageChunk(object _1, RoutedEventArgs _2)
15321532

15331533
var treeGuid = await new Commands.QueryStagedFileBlobGuid(repo.FullPath, change.Path).GetResultAsync();
15341534
var tmpFile = Path.GetTempFileName();
1535-
if (change.Index == Models.ChangeState.Added)
1536-
diff.GenerateNewPatchFromSelection(change, treeGuid, selection, true, tmpFile);
1537-
else if (chunk.Combined)
1535+
if (chunk.Combined)
15381536
diff.GeneratePatchFromSelection(change, treeGuid, selection, true, tmpFile);
15391537
else
15401538
diff.GeneratePatchFromSelectionSingleSide(change, treeGuid, selection, true, chunk.IsOldSide, tmpFile);
@@ -1562,20 +1560,12 @@ private async void OnDiscardChunk(object _1, RoutedEventArgs _2)
15621560
using var lockWatcher = repo.LockWatcher();
15631561

15641562
var tmpFile = Path.GetTempFileName();
1565-
if (change.Index == Models.ChangeState.Added)
1566-
{
1567-
diff.GenerateNewPatchFromSelection(change, null, selection, true, tmpFile);
1568-
}
1569-
else if (chunk.Combined)
1570-
{
1571-
var treeGuid = await new Commands.QueryStagedFileBlobGuid(repo.FullPath, change.Path).GetResultAsync();
1563+
var treeGuid = await new Commands.QueryStagedFileBlobGuid(repo.FullPath, change.Path).GetResultAsync();
1564+
1565+
if (chunk.Combined)
15721566
diff.GeneratePatchFromSelection(change, treeGuid, selection, true, tmpFile);
1573-
}
15741567
else
1575-
{
1576-
var treeGuid = await new Commands.QueryStagedFileBlobGuid(repo.FullPath, change.Path).GetResultAsync();
15771568
diff.GeneratePatchFromSelectionSingleSide(change, treeGuid, selection, true, chunk.IsOldSide, tmpFile);
1578-
}
15791569

15801570
await new Commands.Apply(repo.FullPath, tmpFile, true, "nowarn", "--reverse").ExecAsync();
15811571
File.Delete(tmpFile);

0 commit comments

Comments
 (0)