|
|
# Copyright (C) 2010-2023 RhodeCode GmbH
|
|
|
#
|
|
|
# This program is free software: you can redistribute it and/or modify
|
|
|
# it under the terms of the GNU Affero General Public License, version 3
|
|
|
# (only), as published by the Free Software Foundation.
|
|
|
#
|
|
|
# This program 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 Affero General Public License
|
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
#
|
|
|
# This program is dual-licensed. If you wish to learn more about the
|
|
|
# RhodeCode Enterprise Edition, including its added features, Support services,
|
|
|
# and proprietary license terms, please see https://rhodecode.com/licenses/
|
|
|
|
|
|
import datetime
|
|
|
import pytest
|
|
|
|
|
|
from rhodecode.lib.vcs.nodes import FileNode
|
|
|
from rhodecode.tests.vcs.conftest import BackendTestMixin
|
|
|
|
|
|
|
|
|
class TestGetDiffValidation:
|
|
|
def test_raises_on_string_input(self, vcsbackend):
|
|
|
repo = vcsbackend.repo
|
|
|
with pytest.raises(TypeError):
|
|
|
repo.get_diff("1", "2")
|
|
|
|
|
|
def test_raises_if_commits_not_of_this_repository(self, vcsbackend):
|
|
|
repo = vcsbackend.repo
|
|
|
|
|
|
target_repo = vcsbackend.create_repo(number_of_commits=1)
|
|
|
repo_commit = repo[0]
|
|
|
wrong_commit = target_repo[0]
|
|
|
with pytest.raises(ValueError):
|
|
|
repo.get_diff(repo_commit, wrong_commit)
|
|
|
|
|
|
def test_allows_empty_commit(self, vcsbackend):
|
|
|
repo = vcsbackend.repo
|
|
|
commit = repo[0]
|
|
|
repo.get_diff(repo.EMPTY_COMMIT, commit)
|
|
|
|
|
|
def test_raise_if_both_commits_are_empty(self, vcsbackend):
|
|
|
repo = vcsbackend.repo
|
|
|
empty_commit = repo.EMPTY_COMMIT
|
|
|
with pytest.raises(ValueError):
|
|
|
repo.get_diff(empty_commit, empty_commit)
|
|
|
|
|
|
def test_supports_path1_parameter(self, vcsbackend):
|
|
|
repo = vcsbackend.repo
|
|
|
commit = repo[1]
|
|
|
repo.get_diff(repo.EMPTY_COMMIT, commit, path="vcs/__init__.py", path1="vcs/__init__.py")
|
|
|
|
|
|
@pytest.mark.backends("git", "hg")
|
|
|
def test_raises_value_error_if_paths_not_supported(self, vcsbackend):
|
|
|
repo = vcsbackend.repo
|
|
|
commit = repo[1]
|
|
|
with pytest.raises(ValueError):
|
|
|
repo.get_diff(repo.EMPTY_COMMIT, commit, path="trunk/example.py", path1="branches/argparse/example.py")
|
|
|
|
|
|
|
|
|
@pytest.mark.usefixtures("vcs_repository_support")
|
|
|
class TestRepositoryGetDiff(BackendTestMixin):
|
|
|
recreate_repo_per_test = False
|
|
|
|
|
|
@classmethod
|
|
|
def _get_commits(cls):
|
|
|
commits = [
|
|
|
{
|
|
|
"message": "Initial commit",
|
|
|
"author": "Joe Doe <joe.doe@example.com>",
|
|
|
"date": datetime.datetime(2010, 1, 1, 20),
|
|
|
"added": [
|
|
|
FileNode(b"foobar", content=b"foobar"),
|
|
|
FileNode(b"foobar2", content=b"foobar2"),
|
|
|
],
|
|
|
},
|
|
|
{
|
|
|
"message": "Changed foobar, added foobar3",
|
|
|
"author": "Jane Doe <jane.doe@example.com>",
|
|
|
"date": datetime.datetime(2010, 1, 1, 21),
|
|
|
"added": [
|
|
|
FileNode(b"foobar3", content=b"foobar3"),
|
|
|
],
|
|
|
"changed": [
|
|
|
FileNode(b"foobar", b"FOOBAR"),
|
|
|
],
|
|
|
},
|
|
|
{
|
|
|
"message": "Removed foobar, changed foobar3",
|
|
|
"author": "Jane Doe <jane.doe@example.com>",
|
|
|
"date": datetime.datetime(2010, 1, 1, 22),
|
|
|
"changed": [
|
|
|
FileNode(b"foobar3", content=b"FOOBAR\nFOOBAR\nFOOBAR\n"),
|
|
|
],
|
|
|
"removed": [FileNode(b"foobar")],
|
|
|
},
|
|
|
{
|
|
|
"message": "Whitespace changes",
|
|
|
"author": "Jane Doe <jane.doe@example.com>",
|
|
|
"date": datetime.datetime(2010, 1, 1, 23),
|
|
|
"changed": [
|
|
|
FileNode(b"foobar3", content=b"FOOBAR \nFOOBAR\nFOOBAR\n"),
|
|
|
],
|
|
|
},
|
|
|
]
|
|
|
return commits
|
|
|
|
|
|
def test_initial_commit_diff(self):
|
|
|
initial_commit = self.repo[0]
|
|
|
diff = self.repo.get_diff(self.repo.EMPTY_COMMIT, initial_commit)
|
|
|
assert diff.raw.tobytes() == self.first_commit_diffs[self.repo.alias]
|
|
|
|
|
|
def test_second_commit_diff(self):
|
|
|
diff = self.repo.get_diff(self.repo[0], self.repo[1])
|
|
|
assert diff.raw.tobytes() == self.second_commit_diffs[self.repo.alias]
|
|
|
|
|
|
def test_third_commit_diff(self):
|
|
|
diff = self.repo.get_diff(self.repo[1], self.repo[2])
|
|
|
assert diff.raw.tobytes() == self.third_commit_diffs[self.repo.alias]
|
|
|
|
|
|
def test_ignore_whitespace(self):
|
|
|
diff = self.repo.get_diff(self.repo[2], self.repo[3], ignore_whitespace=True)
|
|
|
assert b"@@" not in diff.raw.tobytes()
|
|
|
|
|
|
def test_only_one_file(self):
|
|
|
diff = self.repo.get_diff(self.repo.EMPTY_COMMIT, self.repo[0], path="foobar")
|
|
|
assert b"foobar2" not in diff.raw.tobytes()
|
|
|
|
|
|
def test_context_parameter(self):
|
|
|
first_commit = self.repo.get_commit(commit_idx=0)
|
|
|
diff = self.repo.get_diff(self.repo.EMPTY_COMMIT, first_commit, context=2)
|
|
|
assert diff.raw.tobytes() == self.first_commit_diffs[self.repo.alias]
|
|
|
|
|
|
def test_context_only_one_file(self):
|
|
|
diff = self.repo.get_diff(self.repo.EMPTY_COMMIT, self.repo[0], path="foobar", context=2)
|
|
|
assert diff.raw.tobytes() == self.first_commit_one_file[self.repo.alias]
|
|
|
|
|
|
first_commit_diffs = {
|
|
|
"git": rb"""diff --git a/foobar b/foobar
|
|
|
new file mode 100644
|
|
|
index 0000000..f6ea049
|
|
|
--- /dev/null
|
|
|
+++ b/foobar
|
|
|
@@ -0,0 +1 @@
|
|
|
+foobar
|
|
|
\ No newline at end of file
|
|
|
diff --git a/foobar2 b/foobar2
|
|
|
new file mode 100644
|
|
|
index 0000000..e8c9d6b
|
|
|
--- /dev/null
|
|
|
+++ b/foobar2
|
|
|
@@ -0,0 +1 @@
|
|
|
+foobar2
|
|
|
\ No newline at end of file
|
|
|
""",
|
|
|
"hg": rb"""diff --git a/foobar b/foobar
|
|
|
new file mode 100644
|
|
|
--- /dev/null
|
|
|
+++ b/foobar
|
|
|
@@ -0,0 +1,1 @@
|
|
|
+foobar
|
|
|
\ No newline at end of file
|
|
|
diff --git a/foobar2 b/foobar2
|
|
|
new file mode 100644
|
|
|
--- /dev/null
|
|
|
+++ b/foobar2
|
|
|
@@ -0,0 +1,1 @@
|
|
|
+foobar2
|
|
|
\ No newline at end of file
|
|
|
""",
|
|
|
"svn": b"""Index: foobar
|
|
|
===================================================================
|
|
|
diff --git a/foobar b/foobar
|
|
|
new file mode 10644
|
|
|
--- /dev/null\t(revision 0)
|
|
|
+++ b/foobar\t(revision 1)
|
|
|
@@ -0,0 +1 @@
|
|
|
+foobar
|
|
|
\\ No newline at end of file
|
|
|
Index: foobar2
|
|
|
===================================================================
|
|
|
diff --git a/foobar2 b/foobar2
|
|
|
new file mode 10644
|
|
|
--- /dev/null\t(revision 0)
|
|
|
+++ b/foobar2\t(revision 1)
|
|
|
@@ -0,0 +1 @@
|
|
|
+foobar2
|
|
|
\\ No newline at end of file
|
|
|
""",
|
|
|
}
|
|
|
|
|
|
second_commit_diffs = {
|
|
|
"git": rb"""diff --git a/foobar b/foobar
|
|
|
index f6ea049..389865b 100644
|
|
|
--- a/foobar
|
|
|
+++ b/foobar
|
|
|
@@ -1 +1 @@
|
|
|
-foobar
|
|
|
\ No newline at end of file
|
|
|
+FOOBAR
|
|
|
\ No newline at end of file
|
|
|
diff --git a/foobar3 b/foobar3
|
|
|
new file mode 100644
|
|
|
index 0000000..c11c37d
|
|
|
--- /dev/null
|
|
|
+++ b/foobar3
|
|
|
@@ -0,0 +1 @@
|
|
|
+foobar3
|
|
|
\ No newline at end of file
|
|
|
""",
|
|
|
"hg": rb"""diff --git a/foobar b/foobar
|
|
|
--- a/foobar
|
|
|
+++ b/foobar
|
|
|
@@ -1,1 +1,1 @@
|
|
|
-foobar
|
|
|
\ No newline at end of file
|
|
|
+FOOBAR
|
|
|
\ No newline at end of file
|
|
|
diff --git a/foobar3 b/foobar3
|
|
|
new file mode 100644
|
|
|
--- /dev/null
|
|
|
+++ b/foobar3
|
|
|
@@ -0,0 +1,1 @@
|
|
|
+foobar3
|
|
|
\ No newline at end of file
|
|
|
""",
|
|
|
"svn": b"""Index: foobar
|
|
|
===================================================================
|
|
|
diff --git a/foobar b/foobar
|
|
|
--- a/foobar\t(revision 1)
|
|
|
+++ b/foobar\t(revision 2)
|
|
|
@@ -1 +1 @@
|
|
|
-foobar
|
|
|
\\ No newline at end of file
|
|
|
+FOOBAR
|
|
|
\\ No newline at end of file
|
|
|
Index: foobar3
|
|
|
===================================================================
|
|
|
diff --git a/foobar3 b/foobar3
|
|
|
new file mode 10644
|
|
|
--- /dev/null\t(revision 0)
|
|
|
+++ b/foobar3\t(revision 2)
|
|
|
@@ -0,0 +1 @@
|
|
|
+foobar3
|
|
|
\\ No newline at end of file
|
|
|
""",
|
|
|
}
|
|
|
|
|
|
third_commit_diffs = {
|
|
|
"git": rb"""diff --git a/foobar b/foobar
|
|
|
deleted file mode 100644
|
|
|
index 389865b..0000000
|
|
|
--- a/foobar
|
|
|
+++ /dev/null
|
|
|
@@ -1 +0,0 @@
|
|
|
-FOOBAR
|
|
|
\ No newline at end of file
|
|
|
diff --git a/foobar3 b/foobar3
|
|
|
index c11c37d..f932447 100644
|
|
|
--- a/foobar3
|
|
|
+++ b/foobar3
|
|
|
@@ -1 +1,3 @@
|
|
|
-foobar3
|
|
|
\ No newline at end of file
|
|
|
+FOOBAR
|
|
|
+FOOBAR
|
|
|
+FOOBAR
|
|
|
""",
|
|
|
"hg": rb"""diff --git a/foobar b/foobar
|
|
|
deleted file mode 100644
|
|
|
--- a/foobar
|
|
|
+++ /dev/null
|
|
|
@@ -1,1 +0,0 @@
|
|
|
-FOOBAR
|
|
|
\ No newline at end of file
|
|
|
diff --git a/foobar3 b/foobar3
|
|
|
--- a/foobar3
|
|
|
+++ b/foobar3
|
|
|
@@ -1,1 +1,3 @@
|
|
|
-foobar3
|
|
|
\ No newline at end of file
|
|
|
+FOOBAR
|
|
|
+FOOBAR
|
|
|
+FOOBAR
|
|
|
""",
|
|
|
"svn": b"""Index: foobar
|
|
|
===================================================================
|
|
|
diff --git a/foobar b/foobar
|
|
|
deleted file mode 10644
|
|
|
--- a/foobar\t(revision 2)
|
|
|
+++ /dev/null\t(revision 3)
|
|
|
@@ -1 +0,0 @@
|
|
|
-FOOBAR
|
|
|
\\ No newline at end of file
|
|
|
Index: foobar3
|
|
|
===================================================================
|
|
|
diff --git a/foobar3 b/foobar3
|
|
|
--- a/foobar3\t(revision 2)
|
|
|
+++ b/foobar3\t(revision 3)
|
|
|
@@ -1 +1,3 @@
|
|
|
-foobar3
|
|
|
\\ No newline at end of file
|
|
|
+FOOBAR
|
|
|
+FOOBAR
|
|
|
+FOOBAR
|
|
|
""",
|
|
|
}
|
|
|
|
|
|
first_commit_one_file = {
|
|
|
"git": rb"""diff --git a/foobar b/foobar
|
|
|
new file mode 100644
|
|
|
index 0000000..f6ea049
|
|
|
--- /dev/null
|
|
|
+++ b/foobar
|
|
|
@@ -0,0 +1 @@
|
|
|
+foobar
|
|
|
\ No newline at end of file
|
|
|
""",
|
|
|
"hg": rb"""diff --git a/foobar b/foobar
|
|
|
new file mode 100644
|
|
|
--- /dev/null
|
|
|
+++ b/foobar
|
|
|
@@ -0,0 +1,1 @@
|
|
|
+foobar
|
|
|
\ No newline at end of file
|
|
|
""",
|
|
|
"svn": b"""Index: foobar
|
|
|
===================================================================
|
|
|
diff --git a/foobar b/foobar
|
|
|
new file mode 10644
|
|
|
--- /dev/null\t(revision 0)
|
|
|
+++ b/foobar\t(revision 1)
|
|
|
@@ -0,0 +1 @@
|
|
|
+foobar
|
|
|
\\ No newline at end of file
|
|
|
""",
|
|
|
}
|
|
|
|
|
|
|
|
|
class TestSvnGetDiff(object):
|
|
|
@pytest.mark.parametrize(
|
|
|
"path, path1", [("trunk/example.py", "tags/v0.2/example.py"), ("trunk", "tags/v0.2")], ids=["file", "dir"]
|
|
|
)
|
|
|
def test_diff_to_tagged_version(self, vcsbackend_svn, path, path1):
|
|
|
repo = vcsbackend_svn["svn-simple-layout"]
|
|
|
commit1 = repo[-2]
|
|
|
commit2 = repo[-1]
|
|
|
diff = repo.get_diff(commit1, commit2, path=path, path1=path1)
|
|
|
assert diff.raw.tobytes() == self.expected_diff_v_0_2
|
|
|
|
|
|
expected_diff_v_0_2 = b'''Index: example.py
|
|
|
===================================================================
|
|
|
diff --git a/example.py b/example.py
|
|
|
--- a/example.py\t(revision 25)
|
|
|
+++ b/example.py\t(revision 26)
|
|
|
@@ -7,8 +7,12 @@
|
|
|
|
|
|
@click.command()
|
|
|
def main():
|
|
|
+ """
|
|
|
+ Will print out a useful message on invocation.
|
|
|
+ """
|
|
|
click.echo("Hello world!")
|
|
|
|
|
|
|
|
|
+# Main entry point
|
|
|
if __name__ == '__main__':
|
|
|
main()
|
|
|
'''
|
|
|
|
|
|
def test_diff_of_moved_directory(self, vcsbackend_svn):
|
|
|
repo = vcsbackend_svn["svn-move-directory"]
|
|
|
diff = repo.get_diff(repo[0], repo[1])
|
|
|
# TODO: johbo: Think about supporting svn directory nodes
|
|
|
# a little bit better, source is here like a file
|
|
|
expected_diff = b"""Index: source
|
|
|
===================================================================
|
|
|
diff --git a/source b/source
|
|
|
deleted file mode 10644
|
|
|
--- a/source\t(revision 1)
|
|
|
+++ /dev/null\t(revision 2)
|
|
|
Index: target/file
|
|
|
===================================================================
|
|
|
diff --git a/target/file b/target/file
|
|
|
new file mode 10644
|
|
|
--- /dev/null\t(revision 0)
|
|
|
+++ b/target/file\t(revision 2)
|
|
|
"""
|
|
|
assert diff.raw.tobytes() == expected_diff
|
|
|
|
|
|
|
|
|
@pytest.mark.usefixtures("vcs_repository_support")
|
|
|
class TestGetDiffBinary(BackendTestMixin):
|
|
|
recreate_repo_per_test = False
|
|
|
|
|
|
# Note: "Fake" PNG files, has the correct magic as prefix
|
|
|
BINARY = b"""\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00"""
|
|
|
BINARY2 = b"""\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x01\x00\x00"""
|
|
|
|
|
|
@staticmethod
|
|
|
def _get_commits():
|
|
|
commits = [
|
|
|
{
|
|
|
"message": "Add binary file image.png",
|
|
|
"author": "Joe Doe <joe.deo@example.com>",
|
|
|
"date": datetime.datetime(2010, 1, 1, 20),
|
|
|
"added": [
|
|
|
FileNode(b"image.png", content=TestGetDiffBinary.BINARY),
|
|
|
],
|
|
|
},
|
|
|
{
|
|
|
"message": "Modify image.png",
|
|
|
"author": "Joe Doe <joe.deo@example.com>",
|
|
|
"date": datetime.datetime(2010, 1, 1, 21),
|
|
|
"changed": [
|
|
|
FileNode(b"image.png", content=TestGetDiffBinary.BINARY2),
|
|
|
],
|
|
|
},
|
|
|
{
|
|
|
"message": "Remove image.png",
|
|
|
"author": "Joe Doe <joe.deo@example.com>",
|
|
|
"date": datetime.datetime(2010, 1, 1, 21),
|
|
|
"removed": [
|
|
|
FileNode(b"image.png"),
|
|
|
],
|
|
|
},
|
|
|
]
|
|
|
return commits
|
|
|
|
|
|
def test_add_a_binary_file(self):
|
|
|
diff = self.repo.get_diff(self.repo.EMPTY_COMMIT, self.repo[0])
|
|
|
|
|
|
expected = {
|
|
|
"git": b"""diff --git a/image.png b/image.png
|
|
|
new file mode 100644
|
|
|
index 0000000000000000000000000000000000000000..28380fd4a25c58be1b68b523ba2a314f4459ee9c
|
|
|
GIT binary patch
|
|
|
literal 19
|
|
|
Yc%17D@N?(olHy`uVBq!ia0vp^03%2O-T(jq
|
|
|
|
|
|
literal 0
|
|
|
Hc$@<O00001
|
|
|
|
|
|
""",
|
|
|
"hg": b"""diff --git a/image.png b/image.png
|
|
|
new file mode 100644
|
|
|
index 0000000000000000000000000000000000000000..28380fd4a25c58be1b68b523ba2a314f4459ee9c
|
|
|
GIT binary patch
|
|
|
literal 19
|
|
|
Yc%17D@N?(olHy`uVBq!ia0vp^03%2O-T(jq
|
|
|
|
|
|
""",
|
|
|
"svn": b"""===================================================================
|
|
|
Cannot display: file marked as a binary type.
|
|
|
svn:mime-type = application/octet-stream
|
|
|
Index: image.png
|
|
|
===================================================================
|
|
|
diff --git a/image.png b/image.png
|
|
|
new file mode 10644
|
|
|
--- /dev/null\t(revision 0)
|
|
|
+++ b/image.png\t(revision 1)
|
|
|
""",
|
|
|
}
|
|
|
assert diff.raw.tobytes() == expected[self.repo.alias]
|
|
|
|
|
|
def test_update_a_binary_file(self):
|
|
|
diff = self.repo.get_diff(self.repo[0], self.repo[1])
|
|
|
|
|
|
expected = {
|
|
|
"git": b"""diff --git a/image.png b/image.png
|
|
|
index 28380fd4a25c58be1b68b523ba2a314f4459ee9c..1008a77cd372386a1c24fbd96019333f67ad0065 100644
|
|
|
GIT binary patch
|
|
|
literal 19
|
|
|
ac%17D@N?(olHy`uVBq!ia0y~$U;qFkO9I~j
|
|
|
|
|
|
literal 19
|
|
|
Yc%17D@N?(olHy`uVBq!ia0vp^03%2O-T(jq
|
|
|
|
|
|
""",
|
|
|
"hg": b"""diff --git a/image.png b/image.png
|
|
|
index 28380fd4a25c58be1b68b523ba2a314f4459ee9c..1008a77cd372386a1c24fbd96019333f67ad0065
|
|
|
GIT binary patch
|
|
|
literal 19
|
|
|
ac%17D@N?(olHy`uVBq!ia0y~$U;qFkO9I~j
|
|
|
|
|
|
""",
|
|
|
"svn": b"""===================================================================
|
|
|
Cannot display: file marked as a binary type.
|
|
|
svn:mime-type = application/octet-stream
|
|
|
Index: image.png
|
|
|
===================================================================
|
|
|
diff --git a/image.png b/image.png
|
|
|
--- a/image.png\t(revision 1)
|
|
|
+++ b/image.png\t(revision 2)
|
|
|
""",
|
|
|
}
|
|
|
assert diff.raw.tobytes() == expected[self.repo.alias]
|
|
|
|
|
|
def test_remove_a_binary_file(self):
|
|
|
diff = self.repo.get_diff(self.repo[1], self.repo[2])
|
|
|
|
|
|
expected = {
|
|
|
"git": b"""diff --git a/image.png b/image.png
|
|
|
deleted file mode 100644
|
|
|
index 1008a77cd372386a1c24fbd96019333f67ad0065..0000000000000000000000000000000000000000
|
|
|
GIT binary patch
|
|
|
literal 0
|
|
|
Hc$@<O00001
|
|
|
|
|
|
literal 19
|
|
|
ac%17D@N?(olHy`uVBq!ia0y~$U;qFkO9I~j
|
|
|
|
|
|
""",
|
|
|
"hg": b"""diff --git a/image.png b/image.png
|
|
|
deleted file mode 100644
|
|
|
index 1008a77cd372386a1c24fbd96019333f67ad0065..0000000000000000000000000000000000000000
|
|
|
GIT binary patch
|
|
|
literal 0
|
|
|
Hc$@<O00001
|
|
|
|
|
|
""",
|
|
|
"svn": b"""===================================================================
|
|
|
Cannot display: file marked as a binary type.
|
|
|
svn:mime-type = application/octet-stream
|
|
|
Index: image.png
|
|
|
===================================================================
|
|
|
diff --git a/image.png b/image.png
|
|
|
deleted file mode 10644
|
|
|
--- a/image.png\t(revision 2)
|
|
|
+++ /dev/null\t(revision 3)
|
|
|
""",
|
|
|
}
|
|
|
assert diff.raw.tobytes() == expected[self.repo.alias]
|
|
|
|