Skip to content
30 changes: 20 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,24 +108,34 @@ jsdiff's diff functions all take an old text and a new text and perform three st
* `createTwoFilesPatch(oldFileName, newFileName, oldStr, newStr[, oldHeader[, newHeader[, options]]])` - creates a unified diff patch by first computing a diff with `diffLines` and then serializing it to unified diff format.

Parameters:
* `oldFileName` : String to be output in the filename section of the patch for the removals
* `newFileName` : String to be output in the filename section of the patch for the additions
* `oldStr` : Original string value
* `newStr` : New string value
* `oldHeader` : Optional additional information to include in the old file header. Default: `undefined`.
* `newHeader` : Optional additional information to include in the new file header. Default: `undefined`.
* `options` : An object with options.
- `context` describes how many lines of context should be included. You can set this to `Number.MAX_SAFE_INTEGER` or `Infinity` to include the entire file content in one hunk.
* `oldFileName`: String to be output in the filename section of the patch for the removals
* `newFileName`: String to be output in the filename section of the patch for the additions
* `oldStr`: Original string value
* `newStr`: New string value
* `oldHeader`: Optional additional information to include in the old file header. Default: `undefined`.
* `newHeader`: Optional additional information to include in the new file header. Default: `undefined`.
* `options`: An object with options.
- `context`: describes how many lines of context should be included. You can set this to `Number.MAX_SAFE_INTEGER` or `Infinity` to include the entire file content in one hunk.
- `ignoreWhitespace`: Same as in `diffLines`. Defaults to `false`.
- `stripTrailingCr`: Same as in `diffLines`. Defaults to `false`.
- `headerOptions`: Configures the format of patch headers in the returned patch. (Note these are distinct from *hunk* headers, which are a mandatory part of the unified diff format and not configurable.) Has three subfields (all default to `true`):
- `includeIndex`: whether to include a line like `Index: filename.txt` at the start of the patch header. (Even if this is `true`, this line will be omitted if `oldFileName` and `newFileName` are not identical.)
- `includeUnderline`: whether to include `===================================================================`.
- `includeFileHeaders`: whether to include two lines indicating the old and new filename, formatted like `--- old.txt` and `+++ new.txt`.

Note further that jsdiff exports three top-level constants that can be used as `headerOptions` values, named `INCLUDE_HEADERS` (the default), `FILE_HEADERS_ONLY`, and `OMIT_HEADERS`.

(Note that in the case where `includeIndex` and `includeFileHeaders` are both false, the `oldFileName` and `newFileName` parameters are ignored entirely.)

The GNU `patch` util will accept patches produced with any configuration of these header options (and refers to patch headers as "leading garbage", which in typical usage it makes no attempt to parse or use in any way). However, other tools for working with unified diff format patches may be less liberal (and are not unambiguously wrong to be so, since the format has no real standard). Tinkering with the `headerOptions` setting thus provides a way to help make patches produced by jsdiff compatible with other tools.

* `createPatch(fileName, oldStr, newStr[, oldHeader[, newHeader[, options]]])` - creates a unified diff patch.

Just like createTwoFilesPatch, but with oldFileName being equal to newFileName.

* `formatPatch(patch)` - creates a unified diff patch.
* `formatPatch(patch[, headerOptions])` - creates a unified diff patch.

`patch` may be either a single structured patch object (as returned by `structuredPatch`) or an array of them (as returned by `parsePatch`).
`patch` may be either a single structured patch object (as returned by `structuredPatch`) or an array of them (as returned by `parsePatch`). The optional `headerOptions` argument behaves the same as the `headerOptions` option of `createTwoFilesPatch`.

* `structuredPatch(oldFileName, newFileName, oldStr, newStr[, oldHeader[, newHeader[, options]]])` - returns an object with an array of hunk objects.

Expand Down
1 change: 1 addition & 0 deletions release-notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

- [#631](https://github.com/kpdecker/jsdiff/pull/631) - **fix support for using an `Intl.Segmenter` with `diffWords`**. This has been almost completely broken since the feature was added in v6.0.0, since it would outright crash on any text that featured two consecutive newlines between a pair of words (a very common case).
- [#635](https://github.com/kpdecker/jsdiff/pull/635) - **small tweaks to tokenization behaviour of `diffWords`** when used *without* an `Intl.Segmenter`. Specifically, the soft hyphen (U+00AD) is no longer considered to be a word break, and the multiplication and division signs (`×` and `÷`) are now treated as punctuation instead of as letters / word characters.
- [#641](https://github.com/kpdecker/jsdiff/pull/641) - **the format of file headers in `createPatch` etc. patches can now be customised somewhat**. It now takes a `headerOptions` option that can be used to disable the file headers entirely, or omit the `Index:` line and/or the underline. In particular, this was motivated by a request to make jsdiff patches compatible with react-diff-view, which they now are if produced with `headerOptions: FILE_HEADERS_ONLY`.
Copy link

Copilot AI Dec 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The word "customised" uses British English spelling, while the document also uses American English spelling elsewhere (e.g., "behavior" on lines 33, 57, 63, 67, 73). Consider using "customized" (American English spelling) for consistency with parts of the document that use American spelling, or alternatively establish a consistent spelling convention throughout the document.

Suggested change
- [#641](https://github.com/kpdecker/jsdiff/pull/641) - **the format of file headers in `createPatch` etc. patches can now be customised somewhat**. It now takes a `headerOptions` option that can be used to disable the file headers entirely, or omit the `Index:` line and/or the underline. In particular, this was motivated by a request to make jsdiff patches compatible with react-diff-view, which they now are if produced with `headerOptions: FILE_HEADERS_ONLY`.
- [#641](https://github.com/kpdecker/jsdiff/pull/641) - **the format of file headers in `createPatch` etc. patches can now be customized somewhat**. It now takes a `headerOptions` option that can be used to disable the file headers entirely, or omit the `Index:` line and/or the underline. In particular, this was motivated by a request to make jsdiff patches compatible with react-diff-view, which they now are if produced with `headerOptions: FILE_HEADERS_ONLY`.

Copilot uses AI. Check for mistakes.

## 8.0.2

Expand Down
14 changes: 11 additions & 3 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,17 @@ import {
structuredPatch,
createTwoFilesPatch,
createPatch,
formatPatch
formatPatch,
INCLUDE_HEADERS,
FILE_HEADERS_ONLY,
OMIT_HEADERS
} from './patch/create.js';
import type {
StructuredPatchOptionsAbortable,
StructuredPatchOptionsNonabortable,
CreatePatchOptionsAbortable,
CreatePatchOptionsNonabortable
CreatePatchOptionsNonabortable,
HeaderOptions
} from './patch/create.js';

import {convertChangesToDMP} from './convert/dmp.js';
Expand Down Expand Up @@ -91,6 +95,9 @@ export {
createTwoFilesPatch,
createPatch,
formatPatch,
INCLUDE_HEADERS,
FILE_HEADERS_ONLY,
OMIT_HEADERS,
applyPatch,
applyPatches,
parsePatch,
Expand Down Expand Up @@ -127,5 +134,6 @@ export type {
StructuredPatchOptionsAbortable,
StructuredPatchOptionsNonabortable,
CreatePatchOptionsAbortable,
CreatePatchOptionsNonabortable
CreatePatchOptionsNonabortable,
HeaderOptions
};
54 changes: 46 additions & 8 deletions src/patch/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,28 @@ import type { StructuredPatch, DiffLinesOptionsAbortable, DiffLinesOptionsNonabo
type StructuredPatchCallbackAbortable = (patch: StructuredPatch | undefined) => void;
type StructuredPatchCallbackNonabortable = (patch: StructuredPatch) => void;

export interface HeaderOptions {
includeIndex: boolean;
includeUnderline: boolean;
includeFileHeaders: boolean;
}

export const INCLUDE_HEADERS = {
includeIndex: true,
includeUnderline: true,
includeFileHeaders: true
};
export const FILE_HEADERS_ONLY = {
includeIndex: false,
includeUnderline: false,
includeFileHeaders: true
};
export const OMIT_HEADERS = {
includeIndex: false,
includeUnderline: false,
includeFileHeaders: false
};

interface _StructuredPatchOptionsAbortable extends Pick<DiffLinesOptionsAbortable, 'ignoreWhitespace' | 'stripTrailingCr'> {
/**
* describes how many lines of context should be included.
Expand Down Expand Up @@ -254,18 +276,32 @@ export function structuredPatch(
* creates a unified diff patch.
* @param patch either a single structured patch object (as returned by `structuredPatch`) or an array of them (as returned by `parsePatch`)
*/
export function formatPatch(patch: StructuredPatch | StructuredPatch[]): string {
export function formatPatch(patch: StructuredPatch | StructuredPatch[], headerOptions?: HeaderOptions): string {
if (!headerOptions) {
headerOptions = INCLUDE_HEADERS;
}
if (Array.isArray(patch)) {
return patch.map(formatPatch).join('\n');
if (patch.length > 1 && !headerOptions.includeFileHeaders) {
throw new Error(
'Cannot omit file headers on a multi-file patch. '
+ '(The result would be unparseable; how would a tool trying to apply '
+ 'the patch know which changes are to which file?)'
);
}
return patch.map(p => formatPatch(p, headerOptions)).join('\n');
}

const ret = [];
if (patch.oldFileName == patch.newFileName) {
if (headerOptions.includeIndex && patch.oldFileName == patch.newFileName) {
ret.push('Index: ' + patch.oldFileName);
}
ret.push('===================================================================');
ret.push('--- ' + patch.oldFileName + (typeof patch.oldHeader === 'undefined' ? '' : '\t' + patch.oldHeader));
ret.push('+++ ' + patch.newFileName + (typeof patch.newHeader === 'undefined' ? '' : '\t' + patch.newHeader));
if (headerOptions.includeUnderline) {
ret.push('===================================================================');
}
if (headerOptions.includeFileHeaders) {
ret.push('--- ' + patch.oldFileName + (typeof patch.oldHeader === 'undefined' ? '' : '\t' + patch.oldHeader));
ret.push('+++ ' + patch.newFileName + (typeof patch.newHeader === 'undefined' ? '' : '\t' + patch.newHeader));
}

for (let i = 0; i < patch.hunks.length; i++) {
const hunk = patch.hunks[i];
Expand Down Expand Up @@ -297,11 +333,13 @@ type CreatePatchCallbackNonabortable = (patch: string) => void;
interface _CreatePatchOptionsAbortable extends Pick<DiffLinesOptionsAbortable, 'ignoreWhitespace' | 'stripTrailingCr'> {
context?: number,
callback?: CreatePatchCallbackAbortable,
headerOptions?: HeaderOptions,
}
export type CreatePatchOptionsAbortable = _CreatePatchOptionsAbortable & AbortableDiffOptions;
export interface CreatePatchOptionsNonabortable extends Pick<DiffLinesOptionsNonabortable, 'ignoreWhitespace' | 'stripTrailingCr'> {
context?: number,
callback?: CreatePatchCallbackNonabortable,
headerOptions?: HeaderOptions,
}
interface CreatePatchCallbackOptionAbortable {
callback: CreatePatchCallbackAbortable;
Expand Down Expand Up @@ -382,7 +420,7 @@ export function createTwoFilesPatch(
if (!patchObj) {
return;
}
return formatPatch(patchObj);
return formatPatch(patchObj, options?.headerOptions);
} else {
const {callback} = options;
structuredPatch(
Expand All @@ -398,7 +436,7 @@ export function createTwoFilesPatch(
if (!patchObj) {
(callback as CreatePatchCallbackAbortable)(undefined);
} else {
callback(formatPatch(patchObj));
callback(formatPatch(patchObj, options.headerOptions));
}
}
}
Expand Down
Loading