Apply unified diff patches to source text with fuzzy matching and error handling. Supports both single patches and batch patch application with configurable tolerance for mismatches.
Applies a unified diff patch to source text with intelligent matching and fuzzy logic.
/**
* Apply a unified diff patch to source text
* @param source - Original text to patch
* @param patch - Unified diff patch string or structured patch object
* @param options - Configuration options
* @returns Patched text on success, false on failure
*/
function applyPatch(source, patch, options);Applies multiple patches using callback-based file loading and saving.
/**
* Apply multiple patches with callback-based file operations
* @param patches - Array of patches or multi-file patch string
* @param options - Configuration with callback functions
*/
function applyPatches(patches, options);interface ApplyPatchOptions {
fuzzFactor?: number; // Maximum line mismatches allowed (default: 0)
autoConvertLineEndings?: boolean; // Auto-convert line endings (default: true)
compareLine?: (lineNumber: number, line: string, operation: string, patchContent: string) => boolean;
}
interface ApplyPatchesOptions {
loadFile: (patch: any, callback: (error: Error | null, content?: string) => void) => void;
patched: (patch: any, content: string | false, callback: (error?: Error) => void) => void;
complete: (error?: Error) => void;
}import { applyPatch, createPatch } from "diff";
// Create a patch
const originalText = "Hello World\nLine 2\nLine 3";
const modifiedText = "Hello Universe\nLine 2\nLine 3";
const patch = createPatch("file.txt", originalText, modifiedText);
// Apply the patch
const result = applyPatch(originalText, patch);
console.log(result === modifiedText); // true
// Failed application returns false
const badResult = applyPatch("completely different text", patch);
console.log(badResult); // falseimport { applyPatch } from "diff";
// Source text has slight differences from when patch was created
const sourceText = `
function hello() {
console.log("Hello World");
return true;
}
function goodbye() {
console.log("Goodbye");
}
`.trim();
// Patch expects slightly different context
const patch = `--- file.js
+++ file.js
@@ -1,4 +1,4 @@
function hello() {
- console.log("Hello World");
+ console.log("Hello Universe");
return true;
}`;
// Apply with fuzzy matching
const result = applyPatch(sourceText, patch, {
fuzzFactor: 2 // Allow up to 2 mismatched context lines
});
console.log(result); // Successfully patched despite context differencesimport { applyPatch } from "diff";
// Source has Unix line endings
const unixSource = "line1\nline2\nline3\n";
// Patch was created from Windows line endings
const windowsPatch = `--- file.txt
+++ file.txt
@@ -1,3 +1,3 @@
line1\r
-line2\r
+modified line2\r
line3\r`;
// Auto-convert line endings (default behavior)
const result = applyPatch(unixSource, windowsPatch, {
autoConvertLineEndings: true
});
console.log(result); // Successfully applied despite line ending differencesimport { applyPatches } from "diff";
import fs from "fs";
// Multi-file patch string
const multiPatch = `--- file1.txt
+++ file1.txt
@@ -1,2 +1,2 @@
-old content
+new content
unchanged line
--- file2.txt
+++ file2.txt
@@ -1,1 +1,2 @@
existing line
+added line`;
applyPatches(multiPatch, {
loadFile: (patch, callback) => {
try {
const content = fs.readFileSync(patch.oldFileName, 'utf8');
callback(null, content);
} catch (error) {
callback(error);
}
},
patched: (patch, patchedContent, callback) => {
if (patchedContent === false) {
callback(new Error(`Failed to apply patch to ${patch.oldFileName}`));
return;
}
try {
fs.writeFileSync(patch.oldFileName, patchedContent);
console.log(`Successfully patched ${patch.oldFileName}`);
callback();
} catch (error) {
callback(error);
}
},
complete: (error) => {
if (error) {
console.error("Patch application failed:", error);
} else {
console.log("All patches applied successfully");
}
}
});import { applyPatch } from "diff";
// Custom comparison function for case-insensitive matching
const result = applyPatch(source, patch, {
compareLine: (lineNumber, line, operation, patchContent) => {
// Case-insensitive comparison
return line.toLowerCase() === patchContent.toLowerCase();
}
});
// Whitespace-tolerant comparison
const whitespaceResult = applyPatch(source, patch, {
compareLine: (lineNumber, line, operation, patchContent) => {
return line.trim() === patchContent.trim();
}
});import { applyPatches } from "diff";
import path from "path";
function applyPatchesToDirectory(patchContent, baseDirectory) {
const results = {
successful: [],
failed: [],
errors: []
};
return new Promise((resolve, reject) => {
applyPatches(patchContent, {
loadFile: (patch, callback) => {
const filePath = path.join(baseDirectory, patch.oldFileName);
fs.readFile(filePath, 'utf8', (error, content) => {
if (error) {
results.errors.push({ file: patch.oldFileName, error: error.message });
callback(error);
} else {
callback(null, content);
}
});
},
patched: (patch, patchedContent, callback) => {
if (patchedContent === false) {
results.failed.push(patch.oldFileName);
callback(new Error(`Patch failed for ${patch.oldFileName}`));
return;
}
const filePath = path.join(baseDirectory, patch.oldFileName);
fs.writeFile(filePath, patchedContent, 'utf8', (error) => {
if (error) {
results.errors.push({ file: patch.oldFileName, error: error.message });
callback(error);
} else {
results.successful.push(patch.oldFileName);
callback();
}
});
},
complete: (error) => {
if (error && results.successful.length === 0) {
reject(error);
} else {
resolve(results);
}
}
});
});
}
// Usage
applyPatchesToDirectory(largePatch, './src')
.then(results => {
console.log(`Applied patches to ${results.successful.length} files`);
if (results.failed.length > 0) {
console.log(`Failed to patch: ${results.failed.join(', ')}`);
}
})
.catch(error => {
console.error("Batch patch application failed:", error);
});import { applyPatch } from "diff";
function validatePatch(source, patch, options = {}) {
const validation = {
canApply: false,
requiredFuzzFactor: 0,
issues: []
};
// Try applying with increasing fuzz factors
for (let fuzz = 0; fuzz <= 10; fuzz++) {
const result = applyPatch(source, patch, {
...options,
fuzzFactor: fuzz
});
if (result !== false) {
validation.canApply = true;
validation.requiredFuzzFactor = fuzz;
validation.result = result;
break;
}
}
if (!validation.canApply) {
validation.issues.push("Patch cannot be applied even with maximum fuzz factor");
} else if (validation.requiredFuzzFactor > 0) {
validation.issues.push(`Requires fuzz factor of ${validation.requiredFuzzFactor}`);
}
return validation;
}
// Usage
const patchValidation = validatePatch(sourceCode, patch);
if (patchValidation.canApply) {
console.log("Patch can be applied");
if (patchValidation.requiredFuzzFactor > 0) {
console.log(`Warning: Requires fuzzy matching (${patchValidation.requiredFuzzFactor})`);
}
} else {
console.log("Patch cannot be applied:", patchValidation.issues);
}import { applyPatch } from "diff";
function robustPatchApplication(source, patch, options = {}) {
const attempts = [
// First attempt: strict application
{ ...options, fuzzFactor: 0 },
// Second attempt: allow minor mismatches
{ ...options, fuzzFactor: 2 },
// Third attempt: more tolerant matching
{
...options,
fuzzFactor: 5,
compareLine: (lineNumber, line, operation, patchContent) => {
return line.trim() === patchContent.trim();
}
},
// Last attempt: very fuzzy with case insensitive
{
...options,
fuzzFactor: 10,
compareLine: (lineNumber, line, operation, patchContent) => {
return line.toLowerCase().trim() === patchContent.toLowerCase().trim();
}
}
];
for (let i = 0; i < attempts.length; i++) {
const result = applyPatch(source, patch, attempts[i]);
if (result !== false) {
return {
success: true,
result: result,
attempt: i + 1,
strategy: attempts[i]
};
}
}
return {
success: false,
error: "All patch application strategies failed"
};
}
// Usage
const robustResult = robustPatchApplication(sourceText, problemPatch);
if (robustResult.success) {
console.log(`Patch applied using strategy ${robustResult.attempt}`);
} else {
console.error("Patch could not be applied:", robustResult.error);
}import { applyPatch } from "diff";
import { execSync } from "child_process";
function applyGitPatch(patchFile, options = {}) {
try {
// Try using git apply first
execSync(`git apply --check ${patchFile}`);
execSync(`git apply ${patchFile}`);
return { success: true, method: 'git' };
} catch (gitError) {
console.log("Git apply failed, trying manual application...");
// Fallback to manual patch application
const patchContent = fs.readFileSync(patchFile, 'utf8');
const patches = parsePatch(patchContent);
const results = [];
for (const patch of patches) {
const sourceContent = fs.readFileSync(patch.oldFileName, 'utf8');
const result = applyPatch(sourceContent, patch, {
fuzzFactor: 3,
...options
});
if (result !== false) {
fs.writeFileSync(patch.oldFileName, result);
results.push({ file: patch.oldFileName, success: true });
} else {
results.push({ file: patch.oldFileName, success: false });
}
}
return {
success: results.every(r => r.success),
method: 'manual',
results: results
};
}
}