or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

array-diffing.mdcharacter-diffing.mdcss-diffing.mdcustom-diffing.mdformat-conversion.mdindex.mdjson-diffing.mdline-diffing.mdpatch-application.mdpatch-creation.mdpatch-utilities.mdsentence-diffing.mdword-diffing.md
tile.json

patch-application.mddocs/

Patch Application

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.

Capabilities

applyPatch Function

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);

applyPatches Function

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);

Options and Types

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;
}

Usage Examples

Basic Patch Application

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); // false

Fuzzy Patch Application

import { 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 differences

Cross-Platform Line Endings

import { 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 differences

Multiple File Patches

import { 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");
    }
  }
});

Advanced Usage

Custom Line Comparison

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();
  }
});

Batch File Processing

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);
  });

Dry Run and Validation

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);
}

Error Handling and Recovery

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);
}

Version Control Integration

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
    };
  }
}