Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
35 changes: 35 additions & 0 deletions src/diff.pegjs
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,29 @@
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
= diffs:diff* { return diffs }

diff
= binary_merge_conflict_diff
/ rename_or_copy_diff
/ merge_conflict_diff
/ unmerged_path
/ binary_diff
Expand All @@ -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) }

Expand Down Expand Up @@ -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 }

Expand All @@ -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
Expand Down
73 changes: 73 additions & 0 deletions test/diff.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down