Skip to content

Add support for 'C'-type diffs #945

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Oct 19, 2019
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
1 change: 1 addition & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,6 @@ Contributors are:
-Stefan Stancu <stefan.stancu _at_ gmail.com>
-César Izurieta <cesar _at_ caih.org>
-Arthur Milchior <arthur _at_ milchior.fr>
-JJ Graham <thetwoj _at_ gmail.com>

Portions derived from other open source works and are clearly marked.
33 changes: 24 additions & 9 deletions git/diff.py
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ class DiffIndex(list):
# R = Renamed
# M = Modified
# T = Changed in the type
change_type = ("A", "D", "R", "M", "T")
change_type = ("A", "C", "D", "R", "M", "T")

def iter_change_type(self, change_type):
"""
Expand All @@ -193,6 +193,8 @@ def iter_change_type(self, change_type):
yield diff
elif change_type == "D" and diff.deleted_file:
yield diff
elif change_type == "C" and diff.copied_file:
yield diff
elif change_type == "R" and diff.renamed:
yield diff
elif change_type == "M" and diff.a_blob and diff.b_blob and diff.a_blob != diff.b_blob:
Expand Down Expand Up @@ -243,6 +245,9 @@ class Diff(object):
^rename[ ]to[ ](?P<rename_to>.*)(?:\n|$))?
(?:^new[ ]file[ ]mode[ ](?P<new_file_mode>.+)(?:\n|$))?
(?:^deleted[ ]file[ ]mode[ ](?P<deleted_file_mode>.+)(?:\n|$))?
(?:^similarity[ ]index[ ]\d+%\n
^copy[ ]from[ ].*\n
^copy[ ]to[ ](?P<copied_file_name>.*)(?:\n|$))?
(?:^index[ ](?P<a_blob_id>[0-9A-Fa-f]+)
\.\.(?P<b_blob_id>[0-9A-Fa-f]+)[ ]?(?P<b_mode>.+)?(?:\n|$))?
(?:^---[ ](?P<a_path>[^\t\n\r\f\v]*)[\t\r\f\v]*(?:\n|$))?
Expand All @@ -253,11 +258,11 @@ class Diff(object):
NULL_BIN_SHA = b"\0" * 20

__slots__ = ("a_blob", "b_blob", "a_mode", "b_mode", "a_rawpath", "b_rawpath",
"new_file", "deleted_file", "raw_rename_from", "raw_rename_to",
"diff", "change_type", "score")
"new_file", "deleted_file", "copied_file", "raw_rename_from",
"raw_rename_to", "diff", "change_type", "score")

def __init__(self, repo, a_rawpath, b_rawpath, a_blob_id, b_blob_id, a_mode,
b_mode, new_file, deleted_file, raw_rename_from,
b_mode, new_file, deleted_file, copied_file, raw_rename_from,
raw_rename_to, diff, change_type, score):

self.a_mode = a_mode
Expand Down Expand Up @@ -285,6 +290,7 @@ def __init__(self, repo, a_rawpath, b_rawpath, a_blob_id, b_blob_id, a_mode,

self.new_file = new_file
self.deleted_file = deleted_file
self.copied_file = copied_file

# be clear and use None instead of empty strings
assert raw_rename_from is None or isinstance(raw_rename_from, binary_type)
Expand Down Expand Up @@ -336,6 +342,8 @@ def __str__(self):
msg += '\nfile deleted in rhs'
if self.new_file:
msg += '\nfile added in rhs'
if self.copied_file:
msg += '\nfile %r copied from %r' % (self.b_path, self.a_path)
if self.rename_from:
msg += '\nfile renamed from %r' % self.rename_from
if self.rename_to:
Expand Down Expand Up @@ -419,11 +427,12 @@ def _index_from_patch_format(cls, repo, proc):
a_path_fallback, b_path_fallback, \
old_mode, new_mode, \
rename_from, rename_to, \
new_file_mode, deleted_file_mode, \
new_file_mode, deleted_file_mode, copied_file_name, \
a_blob_id, b_blob_id, b_mode, \
a_path, b_path = header.groups()

new_file, deleted_file = bool(new_file_mode), bool(deleted_file_mode)
new_file, deleted_file, copied_file = \
bool(new_file_mode), bool(deleted_file_mode), bool(copied_file_name)

a_path = cls._pick_best_path(a_path, rename_from, a_path_fallback)
b_path = cls._pick_best_path(b_path, rename_to, b_path_fallback)
Expand All @@ -445,7 +454,7 @@ def _index_from_patch_format(cls, repo, proc):
b_blob_id and b_blob_id.decode(defenc),
a_mode and a_mode.decode(defenc),
b_mode and b_mode.decode(defenc),
new_file, deleted_file,
new_file, deleted_file, copied_file,
rename_from,
rename_to,
None, None, None))
Expand Down Expand Up @@ -485,6 +494,7 @@ def handle_diff_line(line):
b_path = path.encode(defenc)
deleted_file = False
new_file = False
copied_file = False
rename_from = None
rename_to = None

Expand All @@ -496,6 +506,11 @@ def handle_diff_line(line):
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)
Expand All @@ -507,8 +522,8 @@ def handle_diff_line(line):
# 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, rename_from, rename_to, '',
change_type, score)
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)
Expand Down
4 changes: 4 additions & 0 deletions git/test/fixtures/diff_copied_mode
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
diff --git a/test1.txt b/test2.txt
similarity index 100%
copy from test1.txt
copy to test2.txt
1 change: 1 addition & 0 deletions git/test/fixtures/diff_copied_mode_raw
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
:100644 100644 cfe9deac6e10683917e80f877566b58644aa21df cfe9deac6e10683917e80f877566b58644aa21df C100 test1.txt test2.txt
23 changes: 23 additions & 0 deletions git/test/test_diff.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,29 @@ def test_diff_with_rename(self):
self.assertEqual(diff.score, 100)
self.assertEqual(len(list(diffs.iter_change_type('R'))), 1)

def test_diff_with_copied_file(self):
output = StringProcessAdapter(fixture('diff_copied_mode'))
diffs = Diff._index_from_patch_format(self.rorepo, output)
self._assert_diff_format(diffs)

assert_equal(1, len(diffs))

diff = diffs[0]
assert_true(diff.copied_file)
assert_true(diff.a_path, u'test1.txt')
assert_true(diff.b_path, u'test2.txt')
assert isinstance(str(diff), str)

output = StringProcessAdapter(fixture('diff_copied_mode_raw'))
diffs = Diff._index_from_raw_format(self.rorepo, output)
self.assertEqual(len(diffs), 1)
diff = diffs[0]
self.assertEqual(diff.change_type, 'C')
self.assertEqual(diff.score, 100)
self.assertEqual(diff.a_path, u'test1.txt')
self.assertEqual(diff.b_path, u'test2.txt')
self.assertEqual(len(list(diffs.iter_change_type('C'))), 1)

def test_diff_with_change_in_type(self):
output = StringProcessAdapter(fixture('diff_change_in_type'))
diffs = Diff._index_from_patch_format(self.rorepo, output)
Expand Down