Apply line changes from a unified patch and check for rejected hunks.
dotnet add package RecursiveAI.OneDiffRequires .NET 10 (net10.0); .NET 9 and earlier aren’t supported.
using OneDiff; string oldText = "red\ngreen\n"; string patch = Diff.Unified( oldText, "red\nlime\n"); var result = Diff.ApplyUnified( oldText, patch); if (!result.Success) throw new Exception("Rejected hunks"); Console.Write(result.Text);
red lime
Unzip the example, then dotnet run.
The full program
The program generates a patch from two literal strings, prints it, and applies it. Conflicts return rejected hunks rather than throwing; malformed patch text throws PatchFormatException.
using OneDiff; string oldText = "red\ngreen\nblue\n"; string newText = "red\nlime\nblue\n"; string patch = Diff.Unified(oldText, newText); Console.Write(patch); var result = Diff.ApplyUnified(oldText, patch); Console.WriteLine($"Success: {result.Success}"); foreach (var hunk in result.Hunks) Console.WriteLine($"{hunk.Outcome}: {hunk.Reason}"); if (result.Success) Console.Write(result.Text); else Console.Write(result.Rejected);
dotnet run prints:
--- a +++ b @@ -1,3 +1,3 @@ red -green +lime blue Success: True Applied: None red lime blue
Applied: None means the hunk’s Outcome is Applied and its rejection Reason is None: it applied without a rejection.
Variations
Apply a patch file without saving a partial update. The download includes target.txt and change.patch with these contents:
target.txt:
red green blue
change.patch:
--- a +++ b @@ -1,3 +1,3 @@ red -green +lime blue
Run this replacement program in the example folder. It overwrites target.txt only if every hunk applies.
using OneDiff; string targetPath = "target.txt"; string patch = File.ReadAllText("change.patch"); var result = Diff.ApplyUnified( File.ReadAllText(targetPath), patch); if (result.Success) { File.WriteAllText(targetPath, result.Text); Console.WriteLine("Updated target.txt"); } else { Console.WriteLine("Rejected; target.txt unchanged"); Console.Write(result.Rejected); }
Updated target.txtThe saved file contains red, lime and blue, each on its own line. If any hunk is rejected, the program leaves the target file untouched and prints the rejected patch.
Require exact context and the original position.
using OneDiff; using OneDiff.Patching; string patch = Diff.Unified("a\nb\n", "a\nc\n"); var result = Diff.ApplyUnified("x\nb\n", patch, new UnifiedApplyOptions { MaxFuzz = 0, MaxOffset = 0 }); Console.WriteLine(result.Success); foreach (var hunk in result.Hunks) Console.WriteLine(hunk.Reason);
False ContextMismatch
Detect a patch that looks already applied.
using OneDiff; string patch = Diff.Unified("old\n", "new\n"); var result = Diff.ApplyUnified("new\n", patch); Console.WriteLine(result.Hunks[0].Reason);
AlreadyAppliedReverse the patch.
using OneDiff; using OneDiff.Patching; string patch = Diff.Unified("old\n", "new\n"); var result = Diff.ApplyUnified("new\n", patch, new UnifiedApplyOptions { Reverse = true }); Console.WriteLine(result.Success); Console.Write(result.Text);
True old
Related recipes
The short answer
Call Diff.ApplyUnified(text, patchText) and inspect Success before using Text as the complete updated file. The result includes an outcome for each hunk and a patch containing the rejected hunks. A hunk is a group of changed lines together with context. A parseable patch is not necessarily an applicable patch, so successful parsing alone does not mean the update succeeded.
How do I apply a unified diff patch in C#?
The example uses Diff.Unified only to supply a known patch. In an application, the second argument can instead be unified patch text received from a file or another process. The one-liner accepts a single-file patch; passing a patch containing more than one file throws ArgumentException. It operates on the text you supply, not on paths from the patch header. You decide which file to read and where to write the result.
Matching is line-by-line and exact for the lines being matched. The default applier first looks at the position named by the hunk, then searches nearby positions. If that fails, it can ignore outer context lines, up to two levels of fuzz by default. Fuzz is a relaxation of surrounding context, not arbitrary approximate matching of the changed text. The patch documentation specifies placement and edge-of-file rules.
For a stricter update, set MaxFuzz = 0 to require all context lines to match. Set MaxOffset = 0 as well when the original line position must match. The exact-context variation changes a context line from a to x, so it returns ContextMismatch. Choose this policy deliberately: forbidding offsets also rejects a patch whose otherwise matching block simply moved down the file.
Hunks reports whether each hunk was applied normally, with an offset, with fuzz, or rejected. A rejected hunk has no matched line. ContextMismatch means its old lines could not be found under the allowed placement rules. AlreadyApplied means its new lines were found where its old lines were not; the patch may already have been applied or may look reversed. It is a diagnostic, not permission to mark the whole operation successful.
Partial application is possible. Applied hunks change Text; rejected ones leave their locations unchanged. The full program prints the rejected patch on failure instead of printing the partially updated text as though everything worked. For a file-writing workflow, keep the original file until every hunk has succeeded, or explicitly present the partial result for review. Rejected is null only when there are no rejects.
Line endings matter. Patches split text at line-feed characters and retain terminators; a final line without a newline differs from one with a newline. Avoid normalizing line endings between generating and applying a patch unless that transformation is part of your input policy. Reversing a patch swaps its insertions and deletions, as the final variation shows; it still needs matching input.
Malformed patch syntax is a separate error path. The parsers expose typed parse errors or throw PatchFormatException; ordinary application conflicts are returned in the result. Binary and rename-only patch sections are not supported. This recipe updates text content, not repository metadata or binary files.