# -*- coding: UTF-8 -*- # # Copyright 2010-2019 The pygit2 contributors # # This file is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License, version 2, # as published by the Free Software Foundation. # # In addition to the permissions in the GNU General Public License, # the authors give you unlimited permission to link the compiled # version of this file into combinations with other programs, # and to distribute those combinations without any restriction # coming from the use of this file. (The General Public License # restrictions do apply in other respects; for example, they cover # modification of the file, and distribution when not linked into # a combined executable.) # # This file is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; see the file COPYING. If not, write to # the Free Software Foundation, 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. """Tests for Diff objects.""" from __future__ import absolute_import from __future__ import unicode_literals from itertools import chain import textwrap import pytest import pygit2 from pygit2 import GIT_DIFF_INCLUDE_UNMODIFIED from pygit2 import GIT_DIFF_IGNORE_WHITESPACE, GIT_DIFF_IGNORE_WHITESPACE_EOL from pygit2 import GIT_DIFF_SHOW_BINARY from pygit2 import GIT_DELTA_RENAMED from . import utils COMMIT_SHA1_1 = '5fe808e8953c12735680c257f56600cb0de44b10' COMMIT_SHA1_2 = 'c2792cfa289ae6321ecf2cd5806c2194b0fd070c' COMMIT_SHA1_3 = '2cdae28389c059815e951d0bb9eed6533f61a46b' COMMIT_SHA1_4 = 'ccca47fbb26183e71a7a46d165299b84e2e6c0b3' COMMIT_SHA1_5 = '056e626e51b1fc1ee2182800e399ed8d84c8f082' COMMIT_SHA1_6 = 'f5e5aa4e36ab0fe62ee1ccc6eb8f79b866863b87' COMMIT_SHA1_7 = '784855caf26449a1914d2cf62d12b9374d76ae78' PATCH = """diff --git a/a b/a index 7f129fd..af431f2 100644 --- a/a +++ b/a @@ -1 +1 @@ -a contents 2 +a contents diff --git a/c/d b/c/d deleted file mode 100644 index 297efb8..0000000 --- a/c/d +++ /dev/null @@ -1 +0,0 @@ -c/d contents """ PATCH_BINARY = """diff --git a/binary_file b/binary_file index 86e5c10..b835d73 100644 Binary files a/binary_file and b/binary_file differ """ PATCH_BINARY_SHOW = """diff --git a/binary_file b/binary_file index 86e5c1008b5ce635d3e3fffa4434c5eccd8f00b6..b835d73543244b6694f36a8c5dfdffb71b153db7 100644 GIT binary patch literal 8 Pc${NM%FIhFs^kIy3n&7R literal 8 Pc${NM&PdElPvrst3ey5{ """ DIFF_HEAD_TO_INDEX_EXPECTED = [ 'staged_changes', 'staged_changes_file_deleted', 'staged_changes_file_modified', 'staged_delete', 'staged_delete_file_modified', 'staged_new', 'staged_new_file_deleted', 'staged_new_file_modified' ] DIFF_HEAD_TO_WORKDIR_EXPECTED = [ 'file_deleted', 'modified_file', 'staged_changes', 'staged_changes_file_deleted', 'staged_changes_file_modified', 'staged_delete', 'staged_delete_file_modified', 'subdir/deleted_file', 'subdir/modified_file' ] DIFF_INDEX_TO_WORK_EXPECTED = [ 'file_deleted', 'modified_file', 'staged_changes_file_deleted', 'staged_changes_file_modified', 'staged_new_file_deleted', 'staged_new_file_modified', 'subdir/deleted_file', 'subdir/modified_file' ] HUNK_EXPECTED = """- a contents 2 + a contents """ STATS_EXPECTED = """ a | 2 +- c/d | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) delete mode 100644 c/d """ class DiffDirtyTest(utils.DirtyRepoTestCase): def test_diff_empty_index(self): repo = self.repo head = repo[repo.lookup_reference('HEAD').resolve().target] diff = head.tree.diff_to_index(repo.index) files = [patch.delta.new_file.path for patch in diff] assert DIFF_HEAD_TO_INDEX_EXPECTED == files diff = repo.diff('HEAD', cached=True) files = [patch.delta.new_file.path for patch in diff] assert DIFF_HEAD_TO_INDEX_EXPECTED == files def test_workdir_to_tree(self): repo = self.repo head = repo[repo.lookup_reference('HEAD').resolve().target] diff = head.tree.diff_to_workdir() files = [patch.delta.new_file.path for patch in diff] assert DIFF_HEAD_TO_WORKDIR_EXPECTED == files diff = repo.diff('HEAD') files = [patch.delta.new_file.path for patch in diff] assert DIFF_HEAD_TO_WORKDIR_EXPECTED == files def test_index_to_workdir(self): diff = self.repo.diff() files = [patch.delta.new_file.path for patch in diff] assert DIFF_INDEX_TO_WORK_EXPECTED == files class DiffTest(utils.BareRepoTestCase): def test_diff_invalid(self): commit_a = self.repo[COMMIT_SHA1_1] commit_b = self.repo[COMMIT_SHA1_2] with pytest.raises(TypeError): commit_a.tree.diff_to_tree(commit_b) with pytest.raises(TypeError): commit_a.tree.diff_to_index(commit_b) def test_diff_empty_index(self): repo = self.repo head = repo[repo.lookup_reference('HEAD').resolve().target] diff = self.repo.index.diff_to_tree(head.tree) files = [patch.delta.new_file.path.split('/')[0] for patch in diff] assert [x.name for x in head.tree] == files diff = head.tree.diff_to_index(repo.index) files = [patch.delta.new_file.path.split('/')[0] for patch in diff] assert [x.name for x in head.tree] == files diff = repo.diff('HEAD', cached=True) files = [patch.delta.new_file.path.split('/')[0] for patch in diff] assert [x.name for x in head.tree] == files def test_diff_tree(self): commit_a = self.repo[COMMIT_SHA1_1] commit_b = self.repo[COMMIT_SHA1_2] def _test(diff): assert diff is not None # self.assertIn is 2.7 only assert 2 == sum(map(lambda x: len(x.hunks), diff)) patch = diff[0] hunk = patch.hunks[0] assert hunk.old_start == 1 assert hunk.old_lines == 1 assert hunk.new_start == 1 assert hunk.new_lines == 1 assert patch.delta.old_file.path == 'a' assert patch.delta.new_file.path == 'a' assert patch.delta.is_binary == False _test(commit_a.tree.diff_to_tree(commit_b.tree)) _test(self.repo.diff(COMMIT_SHA1_1, COMMIT_SHA1_2)) def test_diff_empty_tree(self): commit_a = self.repo[COMMIT_SHA1_1] diff = commit_a.tree.diff_to_tree() def get_context_for_lines(diff): hunks = chain(*map(lambda x: x.hunks, [p for p in diff])) lines = chain(*map(lambda x: x.lines, hunks)) return map(lambda x: x.origin, lines) entries = [p.delta.new_file.path for p in diff] assert all(commit_a.tree[x] for x in entries) assert all('-' == x for x in get_context_for_lines(diff)) diff_swaped = commit_a.tree.diff_to_tree(swap=True) entries = [p.delta.new_file.path for p in diff_swaped] assert all(commit_a.tree[x] for x in entries) assert all('+' == x for x in get_context_for_lines(diff_swaped)) def test_diff_revparse(self): diff = self.repo.diff('HEAD', 'HEAD~6') assert type(diff) == pygit2.Diff def test_diff_tree_opts(self): commit_c = self.repo[COMMIT_SHA1_3] commit_d = self.repo[COMMIT_SHA1_4] for flag in [GIT_DIFF_IGNORE_WHITESPACE, GIT_DIFF_IGNORE_WHITESPACE_EOL]: diff = commit_c.tree.diff_to_tree(commit_d.tree, flag) assert diff is not None assert 0 == len(diff[0].hunks) diff = commit_c.tree.diff_to_tree(commit_d.tree) assert diff is not None assert 1 == len(diff[0].hunks) def test_diff_merge(self): commit_a = self.repo[COMMIT_SHA1_1] commit_b = self.repo[COMMIT_SHA1_2] commit_c = self.repo[COMMIT_SHA1_3] diff_b = commit_a.tree.diff_to_tree(commit_b.tree) assert diff_b is not None diff_c = commit_b.tree.diff_to_tree(commit_c.tree) assert diff_c is not None assert 'b' not in [patch.delta.new_file.path for patch in diff_b] assert 'b' in [patch.delta.new_file.path for patch in diff_c] diff_b.merge(diff_c) assert 'b' in [patch.delta.new_file.path for patch in diff_b] patch = diff_b[0] hunk = patch.hunks[0] assert hunk.old_start == 1 assert hunk.old_lines == 1 assert hunk.new_start == 1 assert hunk.new_lines == 1 assert patch.delta.old_file.path == 'a' assert patch.delta.new_file.path == 'a' def test_diff_patch(self): commit_a = self.repo[COMMIT_SHA1_1] commit_b = self.repo[COMMIT_SHA1_2] diff = commit_a.tree.diff_to_tree(commit_b.tree) assert diff.patch == PATCH assert len(diff) == len([patch for patch in diff]) def test_diff_ids(self): commit_a = self.repo[COMMIT_SHA1_1] commit_b = self.repo[COMMIT_SHA1_2] patch = commit_a.tree.diff_to_tree(commit_b.tree)[0] delta = patch.delta assert delta.old_file.id.hex == '7f129fd57e31e935c6d60a0c794efe4e6927664b' assert delta.new_file.id.hex == 'af431f20fc541ed6d5afede3e2dc7160f6f01f16' def test_hunk_content(self): commit_a = self.repo[COMMIT_SHA1_1] commit_b = self.repo[COMMIT_SHA1_2] patch = commit_a.tree.diff_to_tree(commit_b.tree)[0] hunk = patch.hunks[0] lines = ('{0} {1}'.format(x.origin, x.content) for x in hunk.lines) assert HUNK_EXPECTED == ''.join(lines) for line in hunk.lines: assert line.content == line.raw_content.decode() def test_find_similar(self): commit_a = self.repo[COMMIT_SHA1_6] commit_b = self.repo[COMMIT_SHA1_7] #~ Must pass GIT_DIFF_INCLUDE_UNMODIFIED if you expect to emulate #~ --find-copies-harder during rename transformion... diff = commit_a.tree.diff_to_tree(commit_b.tree, GIT_DIFF_INCLUDE_UNMODIFIED) assert all(x.delta.status != GIT_DELTA_RENAMED for x in diff) assert all(x.delta.status_char() != 'R' for x in diff) diff.find_similar() assert any(x.delta.status == GIT_DELTA_RENAMED for x in diff) assert any(x.delta.status_char() == 'R' for x in diff) def test_diff_stats(self): commit_a = self.repo[COMMIT_SHA1_1] commit_b = self.repo[COMMIT_SHA1_2] diff = commit_a.tree.diff_to_tree(commit_b.tree) stats = diff.stats assert 1 == stats.insertions assert 2 == stats.deletions assert 2 == stats.files_changed formatted = stats.format(format=pygit2.GIT_DIFF_STATS_FULL | pygit2.GIT_DIFF_STATS_INCLUDE_SUMMARY, width=80) assert STATS_EXPECTED == formatted def test_deltas(self): commit_a = self.repo[COMMIT_SHA1_1] commit_b = self.repo[COMMIT_SHA1_2] diff = commit_a.tree.diff_to_tree(commit_b.tree) deltas = list(diff.deltas) patches = list(diff) assert len(deltas) == len(patches) for i, delta in enumerate(deltas): patch_delta = patches[i].delta assert delta.status == patch_delta.status assert delta.similarity == patch_delta.similarity assert delta.nfiles == patch_delta.nfiles assert delta.old_file.id == patch_delta.old_file.id assert delta.new_file.id == patch_delta.new_file.id # As explained in the libgit2 documentation, flags are not set #assert delta.flags == patch_delta.flags def test_diff_parse(self): diff = pygit2.Diff.parse_diff(PATCH) stats = diff.stats assert 2 == stats.deletions assert 1 == stats.insertions assert 2 == stats.files_changed deltas = list(diff.deltas) assert 2 == len(deltas) def test_parse_diff_null(self): with pytest.raises(Exception): self.repo.parse_diff(None) def test_parse_diff_bad(self): diff = textwrap.dedent( """ diff --git a/file1 b/file1 old mode 0644 new mode 0644 @@ -1,1 +1,1 @@ -Hi! """) with pytest.raises(Exception): self.repo.parse_diff(diff) class BinaryDiffTest(utils.BinaryFileRepoTestCase): def test_binary_diff(self): repo = self.repo diff = repo.diff('HEAD', 'HEAD^') assert PATCH_BINARY == diff.patch diff = repo.diff('HEAD', 'HEAD^', flags=GIT_DIFF_SHOW_BINARY) assert PATCH_BINARY_SHOW == diff.patch