diff --git a/README.md b/README.md index 2ff57623..66821f87 100644 --- a/README.md +++ b/README.md @@ -137,7 +137,7 @@ jsdiff's diff functions all take an old text and a new text and perform three st `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`, except that it is ignored for Git patches (i.e. patches where `isGit` is `true`). - When a patch has `isGit: true`, `formatPatch` output is changed to more closely match Git's output: it emits a `diff --git` header, emits Git extended headers as appropriate based on properties like `isRename`, `isCreate`, `newMode`, etc, and always emits `---`/`+++` file headers when hunks are present but omits them when there are no hunks (e.g. renames without content changes). The `headerOptions` parameter has no effect on Git patches since the header format is fully determined by the Git extended header properties. + When a patch has `isGit: true`, `formatPatch` output is changed to more closely match Git's output: it emits a `diff --git` header, emits Git extended headers as appropriate based on properties like `isRename`, `isCreate`, `newMode`, etc, and always emits `---`/`+++` file headers when hunks are present but omits them when there are no hunks (e.g. renames without content changes). The `headerOptions` parameter has no effect on Git patches since the header format is fully determined by the Git extended header properties. Binary patches retain a `Binary files ... differ` marker; the formatted patch does not contain the binary file data. * `structuredPatch(oldFileName, newFileName, oldStr, newStr[, oldHeader[, newHeader[, options]]])` - returns an object with an array of hunk objects. diff --git a/src/patch/create.ts b/src/patch/create.ts index 28cf292b..a48e02f3 100644 --- a/src/patch/create.ts +++ b/src/patch/create.ts @@ -411,6 +411,11 @@ export function formatPatch(patch: StructuredPatch | StructuredPatch[], headerOp ret.push('copy from ' + quoteFileNameIfNeeded((patch.oldFileName ?? '').replace(/^a\//, ''))); ret.push('copy to ' + quoteFileNameIfNeeded((patch.newFileName ?? '').replace(/^b\//, ''))); } + if (patch.isBinary) { + const oldName = patch.isCreate ? '/dev/null' : patch.oldFileName; + const newName = patch.isDelete ? '/dev/null' : patch.newFileName; + ret.push('Binary files ' + quoteFileNameIfNeeded(oldName) + ' and ' + quoteFileNameIfNeeded(newName) + ' differ'); + } } else { if (headerOptions.includeIndex && patch.oldFileName == patch.newFileName && patch.oldFileName !== undefined) { ret.push('Index: ' + quoteFileNameIfNeeded(patch.oldFileName)); diff --git a/test/patch/create.js b/test/patch/create.js index 9718135f..375a62b6 100644 --- a/test/patch/create.js +++ b/test/patch/create.js @@ -1354,6 +1354,60 @@ describe('patch/create', function() { expect(formatPatch(patches, OMIT_HEADERS)).to.equal(expected); }); + describe('binary Git patches', function() { + const fixtures = [ + ['modification', + 'diff --git a/old.bin b/new.bin\n' + + 'Binary files a/old.bin and b/new.bin differ\n'], + ['creation', + 'diff --git a/new.bin b/new.bin\n' + + 'new file mode 100644\n' + + 'Binary files /dev/null and b/new.bin differ\n'], + ['deletion', + 'diff --git a/old.bin b/old.bin\n' + + 'deleted file mode 100644\n' + + 'Binary files a/old.bin and /dev/null differ\n'], + ['rename', + 'diff --git a/old.bin b/new.bin\n' + + 'rename from old.bin\n' + + 'rename to new.bin\n' + + 'Binary files a/old.bin and b/new.bin differ\n'], + ['copy', + 'diff --git a/old.bin b/new.bin\n' + + 'copy from old.bin\n' + + 'copy to new.bin\n' + + 'Binary files a/old.bin and b/new.bin differ\n'], + ['quoted filename', + 'diff --git "a/caf\\303\\251\\timage.bin" "b/caf\\303\\251\\timage.bin"\n' + + 'Binary files "a/caf\\303\\251\\timage.bin" and "b/caf\\303\\251\\timage.bin" differ\n'] + ]; + + for (const [name, text] of fixtures) { + it(`should preserve a binary ${name} when formatting`, function() { + const parsed = parsePatch(text); + expect(parsed[0].isBinary).to.equal(true); + expect(formatPatch(parsed)).to.equal(text); + expect(parsePatch(formatPatch(parsed))).to.deep.equal(parsed); + }); + } + + it('should preserve binary flags in a mixed multi-file patch', function() { + const text = fixtures[0][1] + + '\n' + + 'diff --git a/text.txt b/text.txt\n' + + '--- a/text.txt\n' + + '+++ b/text.txt\n' + + '@@ -1,1 +1,1 @@\n' + + '-before\n' + + '+after\n' + + '\n' + fixtures[1][1]; + const parsed = parsePatch(text); + expect(parsed).to.have.length(3); + expect(parsed.map(patch => patch.isBinary)).to.deep.equal([true, undefined, true]); + expect(parsePatch(formatPatch(parsed))).to.deep.equal(parsed); + }); + }); + it('should emit rename headers for patches with isGit and isRename', function() { const patch = { oldFileName: 'a/old.txt',