Files
kandev/apps/web/lib/utils/file-diff.ts
T
João Salavisa 70e6eb8895 fix(git): count diff lines by hunk position, not by "+++"/"---" prefix (#2427)
Co-authored-by: Kandev Agent <agent@kandev.dev>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-08 19:17:09 +02:00

70 lines
2.3 KiB
TypeScript

import { createTwoFilesPatch } from "diff";
import { djb2Hash } from "./hash";
/**
* Generate a unified diff between original and modified content
* @param original - Original file content
* @param modified - Modified file content
* @param filename - File name (used in diff header)
* @returns Unified diff string
*/
export function generateUnifiedDiff(original: string, modified: string, filename: string): string {
// createTwoFilesPatch generates a unified diff with headers
// Parameters: oldFileName, newFileName, oldStr, newStr, oldHeader, newHeader, options
// Note: oldHeader and newHeader should be empty strings or valid timestamps
// The go-diff library on the backend expects standard unified diff format
const diff = createTwoFilesPatch(
filename,
filename,
original,
modified,
"", // Empty oldHeader - backend doesn't need timestamps
"", // Empty newHeader - backend doesn't need timestamps
{ context: 3 }, // Number of context lines
);
return diff;
}
/**
* Calculate hash of content for change detection.
* Uses Web Crypto API (SHA-256) in secure contexts (HTTPS, localhost).
* Falls back to djb2 in non-secure contexts (HTTP) — sufficient for detecting
* content changes, not for security purposes.
* @param content - Content to hash
* @returns Hex-encoded hash string
*/
export async function calculateHash(content: string): Promise<string> {
// crypto.subtle is only available in secure contexts (HTTPS, localhost)
if (typeof crypto !== "undefined" && crypto.subtle) {
try {
const encoder = new TextEncoder();
const data = encoder.encode(content);
const hashBuffer = await crypto.subtle.digest("SHA-256", data);
const hashArray = Array.from(new Uint8Array(hashBuffer));
return hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
} catch {
// Fall through to djb2
}
}
return djb2Hash(content);
}
/**
* Format diff stats for display
* @param additions - Number of additions
* @param deletions - Number of deletions
* @returns Formatted string like "+5 -3"
*/
export function formatDiffStats(additions: number, deletions: number): string {
const parts: string[] = [];
if (additions > 0) {
parts.push(`+${additions}`);
}
if (deletions > 0) {
parts.push(`-${deletions}`);
}
return parts.join(" ") || "No changes";
}