diff --git a/tests/test_parser.py b/tests/test_parser.py index a5c0c47..5cda866 100644 --- a/tests/test_parser.py +++ b/tests/test_parser.py @@ -340,6 +340,29 @@ def test_parse_diff_git_no_prefix(self): self.assertTrue(res[2].is_added_file) self.assertEqual(res[2].path, 'file3') + def test_parse_diff_git_mnemonic_prefix(self): + # issue #81: git diff with diff.mnemonicPrefix set uses i/ w/ (etc.) + # instead of a/ b/; path should still resolve to the plain filename. + diff = ( + 'diff --git i/foo/bar.py w/foo/bar.py\n' + 'index abc1234..def5678 100644\n' + '--- i/foo/bar.py\n' + '+++ w/foo/bar.py\n' + '@@ -1,2 +1,2 @@\n' + ' a\n' + '-b\n' + '+c\n' + ) + res = PatchSet(diff) + + self.assertEqual(len(res), 1) + self.assertEqual(res[0].source_file, 'i/foo/bar.py') + self.assertEqual(res[0].target_file, 'w/foo/bar.py') + self.assertEqual(res[0].path, 'foo/bar.py') + self.assertFalse(res[0].is_rename) + self.assertTrue(res[0].is_modified_file) + self.assertEqual(str(res), diff) + def test_parse_diff_with_empty_filenames(self): # regression test for issue #115: difflib.unified_diff() without # fromfile/tofile emits bare "--- " / "+++ " headers (empty diff --git a/tests/test_patchedfile.py b/tests/test_patchedfile.py index 70edd34..6bc1a80 100644 --- a/tests/test_patchedfile.py +++ b/tests/test_patchedfile.py @@ -50,3 +50,20 @@ def test_is_modified_file(self): hunk = Hunk(src_start=1, src_len=10, tgt_start=1, tgt_len=8) self.patched_file.append(hunk) self.assertTrue(self.patched_file.is_modified_file) + + def test_default_file_prefix(self): + patched_file = PatchedFile(source="a/foo/bar", target="b/foo/bar") + self.assertEqual(patched_file.path, "foo/bar") + + def test_git_mnemonic_file_prefix(self): + # mnemonic prefixes used when diff.mnemonicPrefix is set (c/ i/ o/ w/) + # and the 1/ 2/ pair used by `git diff --no-index` + for prefix in ('c', 'i', 'o', 'w', '1', '2'): + patched_file = PatchedFile(source="%s/foo/bar" % prefix, + target="%s/foo/bar" % prefix) + self.assertEqual(patched_file.path, "foo/bar") + + def test_no_file_prefix(self): + # a leading slash is not a prefix and must be preserved + patched_file = PatchedFile(source="/foo/bar", target="/foo/bar") + self.assertEqual(patched_file.path, "/foo/bar") diff --git a/unidiff/constants.py b/unidiff/constants.py index f3429fc..c85501e 100644 --- a/unidiff/constants.py +++ b/unidiff/constants.py @@ -79,6 +79,11 @@ r'(?P[^\t]+?)(?:\t(?P[\s0-9:\+-]+))?' r'(?: and (?P[^\t]+?)(?:\t(?P[\s0-9:\+-]+))?)? (differ|has changed)') +# git source/target filename prefixes: the standard "a/" and "b/", plus the +# mnemonic prefixes used when diff.mnemonicPrefix is set (c/ i/ o/ w/) and the +# 1/ 2/ pair used by `git diff --no-index` +RE_PATCH_FILE_PREFIX = re.compile(r'^[abciow12]/') + DEFAULT_ENCODING = 'UTF-8' DEV_NULL = '/dev/null' diff --git a/unidiff/patch.py b/unidiff/patch.py index 0be59b5..6105906 100644 --- a/unidiff/patch.py +++ b/unidiff/patch.py @@ -53,6 +53,7 @@ RE_TARGET_FILENAME, RE_NO_NEWLINE_MARKER, RE_BINARY_DIFF, + RE_PATCH_FILE_PREFIX, SYMLINK_FILE_MODE, ) from unidiff.errors import UnidiffParseError @@ -366,7 +367,7 @@ def path(self) -> str: if quoted: filepath = filepath[1:-1] - if filepath.startswith('a/') or filepath.startswith('b/'): + if RE_PATCH_FILE_PREFIX.match(filepath): filepath = filepath[2:] if quoted: