diff --git a/git/diff.py b/git/diff.py index 5af53e556..1d5ace873 100644 --- a/git/diff.py +++ b/git/diff.py @@ -253,17 +253,20 @@ def diff( if paths is not None and not isinstance(paths, (tuple, list)): paths = [paths] - diff_cmd = self.repo.git.diff + # Patch parsing relies on Git's standard "a/" and "b/" path prefixes. + # Override diff.mnemonicPrefix so a user's configuration cannot change them. + git = self.repo.git(c="diff.mnemonicPrefix=false") + diff_cmd = git.diff if other is INDEX: args.insert(0, "--cached") elif other is NULL_TREE: args.insert(0, "-r") # Recursive diff-tree. args.insert(0, "--root") - diff_cmd = self.repo.git.diff_tree + diff_cmd = git.diff_tree elif other is not None: args.insert(0, "-r") # Recursive diff-tree. args.insert(0, other) - diff_cmd = self.repo.git.diff_tree + diff_cmd = git.diff_tree args.insert(0, self) diff --git a/git/index/base.py b/git/index/base.py index f03b452dc..dfa2c5df9 100644 --- a/git/index/base.py +++ b/git/index/base.py @@ -1539,7 +1539,9 @@ def diff( args.extend(paths) kwargs["as_process"] = True - proc = self.repo.git.diff(*args, **kwargs) + # Patch parsing relies on Git's standard "a/" and "b/" path prefixes. + # Override diff.mnemonicPrefix so a user's configuration cannot change them. + proc = self.repo.git(c="diff.mnemonicPrefix=false").diff(*args, **kwargs) diff_method = ( git_diff.Diff._index_from_patch_format if create_patch else git_diff.Diff._index_from_raw_format diff --git a/test/test_diff.py b/test/test_diff.py index 612fbd9e0..8eec20078 100644 --- a/test/test_diff.py +++ b/test/test_diff.py @@ -309,6 +309,35 @@ def test_diff_with_spaces(self): self.assertIsNone(diff_index[0].a_path, repr(diff_index[0].a_path)) self.assertEqual(diff_index[0].b_path, "file with spaces", repr(diff_index[0].b_path)) + @with_rw_directory + def test_diff_ignores_mnemonic_prefix_config(self, rw_dir): + repo = Repo.init(rw_dir) + file_path = osp.join(rw_dir, "file.txt") + with open(file_path, "w", encoding="utf-8") as stream: + stream.write("first\n") + repo.index.add([file_path]) + + with repo.config_writer() as writer: + writer.set_value("diff", "mnemonicPrefix", True) + + # IndexFile has a separate implementation for diffs against the empty tree. + staged = repo.index.diff(NULL_TREE, create_patch=True) + self.assertEqual(len(staged), 1) + self.assertEqual(staged[0].b_path, "file.txt") + + repo.index.commit("first") + with open(file_path, "w", encoding="utf-8") as stream: + stream.write("second\n") + + # Both commit and index diffs use Diffable.diff for the working tree. + for diff_index in ( + repo.head.commit.diff(None, create_patch=True), + repo.index.diff(None, create_patch=True), + ): + self.assertEqual(len(diff_index), 1) + self.assertEqual(diff_index[0].a_path, "file.txt") + self.assertEqual(diff_index[0].b_path, "file.txt") + @pytest.mark.xfail( sys.platform == "win32", reason='"Access is denied" when tearDown calls shutil.rmtree',