diff --git a/git/diff.py b/git/diff.py index 0fc30b9eb..a9dc4b572 100644 --- a/git/diff.py +++ b/git/diff.py @@ -108,6 +108,7 @@ def diff(self, other=Index, paths=None, create_patch=False, **kwargs): args.append("-p") else: args.append("--raw") + args.append("-z") # in any way, assure we don't see colored output, # fixes https://github.com/gitpython-developers/GitPython/issues/172 @@ -478,55 +479,55 @@ def _index_from_raw_format(cls, repo, proc): index = DiffIndex() - def handle_diff_line(line): - line = line.decode(defenc) - if not line.startswith(":"): - return - - meta, _, path = line[1:].partition('\t') - old_mode, new_mode, a_blob_id, b_blob_id, _change_type = meta.split(None, 4) - # Change type can be R100 - # R: status letter - # 100: score (in case of copy and rename) - change_type = _change_type[0] - score_str = ''.join(_change_type[1:]) - score = int(score_str) if score_str.isdigit() else None - path = path.strip() - a_path = path.encode(defenc) - b_path = path.encode(defenc) - deleted_file = False - new_file = False - copied_file = False - rename_from = None - rename_to = None - - # NOTE: We cannot conclude from the existence of a blob to change type - # as diffs with the working do not have blobs yet - if change_type == 'D': - b_blob_id = None - deleted_file = True - elif change_type == 'A': - a_blob_id = None - new_file = True - elif change_type == 'C': - copied_file = True - a_path, b_path = path.split('\t', 1) - a_path = a_path.encode(defenc) - b_path = b_path.encode(defenc) - elif change_type == 'R': - a_path, b_path = path.split('\t', 1) - a_path = a_path.encode(defenc) - b_path = b_path.encode(defenc) - rename_from, rename_to = a_path, b_path - elif change_type == 'T': - # Nothing to do - pass - # END add/remove handling - - diff = Diff(repo, a_path, b_path, a_blob_id, b_blob_id, old_mode, new_mode, - new_file, deleted_file, copied_file, rename_from, rename_to, - '', change_type, score) - index.append(diff) + def handle_diff_line(lines): + lines = lines.decode(defenc) + + for line in lines.split(':')[1:]: + meta, _, path = line.partition('\x00') + path = path.rstrip('\x00') + old_mode, new_mode, a_blob_id, b_blob_id, _change_type = meta.split(None, 4) + # Change type can be R100 + # R: status letter + # 100: score (in case of copy and rename) + change_type = _change_type[0] + score_str = ''.join(_change_type[1:]) + score = int(score_str) if score_str.isdigit() else None + path = path.strip() + a_path = path.encode(defenc) + b_path = path.encode(defenc) + deleted_file = False + new_file = False + copied_file = False + rename_from = None + rename_to = None + + # NOTE: We cannot conclude from the existence of a blob to change type + # as diffs with the working do not have blobs yet + if change_type == 'D': + b_blob_id = None + deleted_file = True + elif change_type == 'A': + a_blob_id = None + new_file = True + elif change_type == 'C': + copied_file = True + a_path, b_path = path.split('\x00', 1) + a_path = a_path.encode(defenc) + b_path = b_path.encode(defenc) + elif change_type == 'R': + a_path, b_path = path.split('\x00', 1) + a_path = a_path.encode(defenc) + b_path = b_path.encode(defenc) + rename_from, rename_to = a_path, b_path + elif change_type == 'T': + # Nothing to do + pass + # END add/remove handling + + diff = Diff(repo, a_path, b_path, a_blob_id, b_blob_id, old_mode, new_mode, + new_file, deleted_file, copied_file, rename_from, rename_to, + '', change_type, score) + index.append(diff) handle_process_output(proc, handle_diff_line, None, finalize_process, decode_streams=False) diff --git a/test/test_diff.py b/test/test_diff.py index 378a58de5..c6c9b67a0 100644 --- a/test/test_diff.py +++ b/test/test_diff.py @@ -26,6 +26,10 @@ import os.path as osp +def to_raw(input): + return input.replace(b'\t', b'\x00') + + @ddt.ddt class TestDiff(TestBase): @@ -112,7 +116,7 @@ def test_diff_with_rename(self): self.assertEqual(diff.raw_rename_to, b'm\xc3\xbcller') assert isinstance(str(diff), str) - output = StringProcessAdapter(fixture('diff_rename_raw')) + output = StringProcessAdapter(to_raw(fixture('diff_rename_raw'))) diffs = Diff._index_from_raw_format(self.rorepo, output) self.assertEqual(len(diffs), 1) diff = diffs[0] @@ -137,7 +141,7 @@ def test_diff_with_copied_file(self): self.assertTrue(diff.b_path, 'test2.txt') assert isinstance(str(diff), str) - output = StringProcessAdapter(fixture('diff_copied_mode_raw')) + output = StringProcessAdapter(to_raw(fixture('diff_copied_mode_raw'))) diffs = Diff._index_from_raw_format(self.rorepo, output) self.assertEqual(len(diffs), 1) diff = diffs[0] @@ -165,7 +169,7 @@ def test_diff_with_change_in_type(self): self.assertIsNotNone(diff.new_file) assert isinstance(str(diff), str) - output = StringProcessAdapter(fixture('diff_change_in_type_raw')) + output = StringProcessAdapter(to_raw(fixture('diff_change_in_type_raw'))) diffs = Diff._index_from_raw_format(self.rorepo, output) self.assertEqual(len(diffs), 1) diff = diffs[0] @@ -175,7 +179,7 @@ def test_diff_with_change_in_type(self): self.assertEqual(len(list(diffs.iter_change_type('T'))), 1) def test_diff_of_modified_files_not_added_to_the_index(self): - output = StringProcessAdapter(fixture('diff_abbrev-40_full-index_M_raw_no-color')) + output = StringProcessAdapter(to_raw(fixture('diff_abbrev-40_full-index_M_raw_no-color'))) diffs = Diff._index_from_raw_format(self.rorepo, output) self.assertEqual(len(diffs), 1, 'one modification')