diff --git a/README.md b/README.md index 19a1e03..c8f7942 100644 --- a/README.md +++ b/README.md @@ -53,3 +53,5 @@ parse(diffStr) ] } ``` + +If the diff includes a similarity index line (from a detected copy or rename), the `similarity` property will be set, and will be a number. diff --git a/src/diff.pegjs b/src/diff.pegjs index b54e73f..e6194ab 100644 --- a/src/diff.pegjs +++ b/src/diff.pegjs @@ -43,6 +43,21 @@ patch.binary = !!binary return patch } + + function postProcessSimilarityDiff (rename_or_copy, similarity_index, old_file, new_file, file_modes, patch) { + const diff = { + oldPath: old_file, + newPath: new_file, + hunks: patch ? patch.hunks : [], + status: rename_or_copy === 'copy' ? 'copied' : 'renamed', + similarity: similarity_index, + } + if (file_modes) { + diff.oldMode = file_modes.old_mode + diff.newMode = file_modes.new_mode + } + return diff + } } diffs @@ -50,6 +65,7 @@ diffs diff = binary_merge_conflict_diff + / rename_or_copy_diff / merge_conflict_diff / unmerged_path / binary_diff @@ -67,6 +83,13 @@ regular_diff binary_merge_conflict_diff = path:merge_conflict_header_line index_line binary_declaration { return postProcessMergeConflictDiff(path, undefined, true) } +rename_or_copy_diff + = rename_or_copy_diff_header_line modes:changed_file_modes? similarity:similarity_index copy_from:rename_copy_from copy_to:rename_copy_to + index_modes:index_line? patch:patch? + { + return postProcessSimilarityDiff(copy_from.operation, similarity, copy_from.file, copy_to.file, modes || index_modes, patch) + } + merge_conflict_diff = path:merge_conflict_header_line index_line patch:patch? { return postProcessMergeConflictDiff(path, patch) } @@ -127,9 +150,15 @@ hunk_line diff_header_line = 'diff ' options:TEXT_NO_SPACES ' ' file_name:file_name_str NL { return {file_name} } +rename_or_copy_diff_header_line + = 'diff ' options:TEXT_NO_SPACES ' ' files_unused:TEXT NL + file_name_str = str:TEXT { return str.substr(str.length/2 + 1) } +similarity_index + = 'similarity index ' idx:NUMBER '%' NL { return idx } + file_mode_section = explicit_file_modes:(new_or_deleted_file_mode / changed_file_modes)? index_file_modes:index_line? { return explicit_file_modes || index_file_modes } @@ -142,6 +171,12 @@ new_or_deleted_file_mode changed_file_modes = 'old mode ' old_mode:TEXT NL 'new mode ' new_mode:TEXT NL { return {old_mode, new_mode} } +rename_copy_from + = operation:('rename' / 'copy') ' from ' file:TEXT NL { return {operation, file} } + +rename_copy_to + = operation:('rename' / 'copy') ' to ' file:TEXT NL { return {operation, file} } + index_line = 'index ' TEXT_NO_SPACES ' ' file_mode:TEXT NL { return {old_mode: file_mode, new_mode: file_mode} } / 'index ' TEXT_NO_SPACES NL diff --git a/test/diff.test.js b/test/diff.test.js index 03f3280..a8a847f 100644 --- a/test/diff.test.js +++ b/test/diff.test.js @@ -543,6 +543,79 @@ exports.testNoPatch = function(test) { test.done() } +exports.testRenameCopy = function(test) { + var str = dedent` + diff --git old/file.png new/file.png + similarity index 90% + rename from old/file.png + rename to new/file.png + diff --git copy/file.png copy/file2.png + similarity index 100% + copy from copy/file.png + copy to copy/file2.png + ` + + const output = diff.parse(str) + assert.deepEqual(output, [ + { + oldPath: 'old/file.png', + newPath: 'new/file.png', + status: 'renamed', + similarity: 90, + hunks: [], + }, + { + oldPath: 'copy/file.png', + newPath: 'copy/file2.png', + status: 'copied', + similarity: 100, + hunks: [], + }, + ]) + test.done() +} + +exports.testRenameWithChangedLinesAndModeChange = function(test) { + var str = dedent` + diff --git file.txt rename-file.txt + old mode 100644 + new mode 100755 + similarity index 76% + rename from file.txt + rename to rename-file.txt + index 471a7b8..3e32ec2 + --- file.txt + +++ rename-file.txt + @@ -1,4 +1,5 @@ + foo + bar + baz + +qux + + `; + + const output = diff.parse(str) + assert.deepEqual(output, [ + { + oldPath: "file.txt", + newPath: "rename-file.txt", + oldMode: "100644", + newMode: "100755", + status: "renamed", + similarity: 76, + hunks: [{ + oldStartLine: 1, + oldLineCount: 4, + newStartLine: 1, + newLineCount: 5, + heading: '', + lines: [' foo', ' bar', ' baz', '+qux'] + }] + } + ]); + test.done() +} + exports.testMergeConflictNoPatch = function(test) { var str = dedent` diff --cc file-0.txt