From f3b6c222fb11c96e2f8bbaa0622f46c8ec486874 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sat, 19 Nov 2022 15:52:50 +0100 Subject: Merging upstream version 0.18.0. Signed-off-by: Daniel Baumann --- gitlint-core/gitlint/tests/git/test_git.py | 65 +-- gitlint-core/gitlint/tests/git/test_git_commit.py | 460 +++++++++++++++------ gitlint-core/gitlint/tests/git/test_git_context.py | 41 +- 3 files changed, 387 insertions(+), 179 deletions(-) (limited to 'gitlint-core/gitlint/tests/git') diff --git a/gitlint-core/gitlint/tests/git/test_git.py b/gitlint-core/gitlint/tests/git/test_git.py index 7b9b7c6..9c73bd9 100644 --- a/gitlint-core/gitlint/tests/git/test_git.py +++ b/gitlint-core/gitlint/tests/git/test_git.py @@ -1,7 +1,6 @@ -# -*- coding: utf-8 -*- import os -from unittest.mock import patch +from unittest.mock import patch, call from gitlint.shell import ErrorReturnCode, CommandNotFound @@ -10,25 +9,23 @@ from gitlint.git import GitContext, GitContextError, GitNotInstalledError, git_c class GitTests(BaseTestCase): - # Expected special_args passed to 'sh' - expected_sh_special_args = { - '_tty_out': False, - '_cwd': "fåke/path" - } + expected_sh_special_args = {"_tty_out": False, "_cwd": "fåke/path"} - @patch('gitlint.git.sh') + @patch("gitlint.git.sh") def test_get_latest_commit_command_not_found(self, sh): sh.git.side_effect = CommandNotFound("git") - expected_msg = "'git' command not found. You need to install git to use gitlint on a local repository. " + \ - "See https://git-scm.com/book/en/v2/Getting-Started-Installing-Git on how to install git." + expected_msg = ( + "'git' command not found. You need to install git to use gitlint on a local repository. " + + "See https://git-scm.com/book/en/v2/Getting-Started-Installing-Git on how to install git." + ) with self.assertRaisesMessage(GitNotInstalledError, expected_msg): GitContext.from_local_repository("fåke/path") # assert that commit message was read using git command sh.git.assert_called_once_with("log", "-1", "--pretty=%H", **self.expected_sh_special_args) - @patch('gitlint.git.sh') + @patch("gitlint.git.sh") def test_get_latest_commit_git_error(self, sh): # Current directory not a git repo err = b"fatal: Not a git repository (or any of the parent directories): .git" @@ -51,10 +48,10 @@ class GitTests(BaseTestCase): # assert that commit message was read using git command sh.git.assert_called_once_with("log", "-1", "--pretty=%H", **self.expected_sh_special_args) - @patch('gitlint.git.sh') + @patch("gitlint.git.sh") def test_git_no_commits_error(self, sh): # No commits: returned by 'git log' - err = b"fatal: your current branch 'master' does not have any commits yet" + err = b"fatal: your current branch 'main' does not have any commits yet" sh.git.side_effect = ErrorReturnCode("git log -1 --pretty=%H", b"", err) @@ -64,25 +61,38 @@ class GitTests(BaseTestCase): # assert that commit message was read using git command sh.git.assert_called_once_with("log", "-1", "--pretty=%H", **self.expected_sh_special_args) - sh.git.reset_mock() + @patch("gitlint.git.sh") + def test_git_no_commits_get_branch(self, sh): + """Check that we can still read the current branch name when there's no commits. This is useful when + when trying to lint the first commit using the --staged flag. + """ # Unknown reference 'HEAD' commits: returned by 'git rev-parse' - err = (b"HEAD" - b"fatal: ambiguous argument 'HEAD': unknown revision or path not in the working tree." - b"Use '--' to separate paths from revisions, like this:" - b"'git [...] -- [...]'") + err = ( + b"HEAD" + b"fatal: ambiguous argument 'HEAD': unknown revision or path not in the working tree." + b"Use '--' to separate paths from revisions, like this:" + b"'git [...] -- [...]'" + ) sh.git.side_effect = [ "#\n", # git config --get core.commentchar - ErrorReturnCode("rev-parse --abbrev-ref HEAD", b"", err) + ErrorReturnCode("rev-parse --abbrev-ref HEAD", b"", err), + "test-branch", # git branch --show-current ] - with self.assertRaisesMessage(GitContextError, expected_msg): - context = GitContext.from_commit_msg("test") - context.current_branch + context = GitContext.from_commit_msg("test") + self.assertEqual(context.current_branch, "test-branch") - # assert that commit message was read using git command - sh.git.assert_called_with("rev-parse", "--abbrev-ref", "HEAD", _tty_out=False, _cwd=None) + # assert that we try using `git rev-parse` first, and if that fails (as will be the case with the first commit), + # we fallback to `git branch --show-current` to determine the current branch name. + expected_calls = [ + call("config", "--get", "core.commentchar", _tty_out=False, _cwd=None, _ok_code=[0, 1]), + call("rev-parse", "--abbrev-ref", "HEAD", _tty_out=False, _cwd=None), + call("branch", "--show-current", _tty_out=False, _cwd=None), + ] + + self.assertEqual(sh.git.mock_calls, expected_calls) @patch("gitlint.git._git") def test_git_commentchar(self, git): @@ -93,11 +103,10 @@ class GitTests(BaseTestCase): git.return_value = "ä" self.assertEqual(git_commentchar(), "ä") - git.return_value = ';\n' - self.assertEqual(git_commentchar(os.path.join("/föo", "bar")), ';') + git.return_value = ";\n" + self.assertEqual(git_commentchar(os.path.join("/föo", "bar")), ";") - git.assert_called_with("config", "--get", "core.commentchar", _ok_code=[0, 1], - _cwd=os.path.join("/föo", "bar")) + git.assert_called_with("config", "--get", "core.commentchar", _ok_code=[0, 1], _cwd=os.path.join("/föo", "bar")) @patch("gitlint.git._git") def test_git_hooks_dir(self, git): diff --git a/gitlint-core/gitlint/tests/git/test_git_commit.py b/gitlint-core/gitlint/tests/git/test_git_commit.py index 02c5795..b27deaf 100644 --- a/gitlint-core/gitlint/tests/git/test_git_commit.py +++ b/gitlint-core/gitlint/tests/git/test_git_commit.py @@ -1,6 +1,6 @@ -# -*- coding: utf-8 -*- import copy import datetime +from pathlib import Path import dateutil @@ -9,29 +9,33 @@ import arrow from unittest.mock import patch, call from gitlint.tests.base import BaseTestCase -from gitlint.git import GitContext, GitCommit, GitContextError, LocalGitCommit, StagedLocalGitCommit, GitCommitMessage +from gitlint.git import ( + GitChangedFileStats, + GitContext, + GitCommit, + GitContextError, + LocalGitCommit, + StagedLocalGitCommit, + GitCommitMessage, + GitChangedFileStats, +) from gitlint.shell import ErrorReturnCode class GitCommitTests(BaseTestCase): - # Expected special_args passed to 'sh' - expected_sh_special_args = { - '_tty_out': False, - '_cwd': "fåke/path" - } + expected_sh_special_args = {"_tty_out": False, "_cwd": "fåke/path"} - @patch('gitlint.git.sh') + @patch("gitlint.git.sh") def test_get_latest_commit(self, sh): sample_sha = "d8ac47e9f2923c7f22d8668e3a1ed04eb4cdbca9" sh.git.side_effect = [ sample_sha, - "test åuthor\x00test-emåil@foo.com\x002016-12-03 15:28:15 +0100\x00åbc\n" - "cömmit-title\n\ncömmit-body", + "test åuthor\x00test-emåil@foo.com\x002016-12-03 15:28:15 +0100\x00åbc\ncömmit-title\n\ncömmit-body", "#", # git config --get core.commentchar - "file1.txt\npåth/to/file2.txt\n", - "foöbar\n* hürdur\n" + "4\t15\tfile1.txt\n-\t-\tpåth/to/file2.bin\n", + "foöbar\n* hürdur\n", ] context = GitContext.from_local_repository("fåke/path") @@ -39,10 +43,17 @@ class GitCommitTests(BaseTestCase): expected_calls = [ call("log", "-1", "--pretty=%H", **self.expected_sh_special_args), call("log", sample_sha, "-1", "--pretty=%aN%x00%aE%x00%ai%x00%P%n%B", **self.expected_sh_special_args), - call('config', '--get', 'core.commentchar', _ok_code=[0, 1], **self.expected_sh_special_args), - call('diff-tree', '--no-commit-id', '--name-only', '-r', '--root', sample_sha, - **self.expected_sh_special_args), - call('branch', '--contains', sample_sha, **self.expected_sh_special_args) + call("config", "--get", "core.commentchar", _ok_code=[0, 1], **self.expected_sh_special_args), + call( + "diff-tree", + "--no-commit-id", + "--numstat", + "-r", + "--root", + sample_sha, + **self.expected_sh_special_args, + ), + call("branch", "--contains", sample_sha, **self.expected_sh_special_args), ] # Only first 'git log' call should've happened at this point @@ -55,18 +66,26 @@ class GitCommitTests(BaseTestCase): self.assertEqual(last_commit.message.body, ["", "cömmit-body"]) self.assertEqual(last_commit.author_name, "test åuthor") self.assertEqual(last_commit.author_email, "test-emåil@foo.com") - self.assertEqual(last_commit.date, datetime.datetime(2016, 12, 3, 15, 28, 15, - tzinfo=dateutil.tz.tzoffset("+0100", 3600))) + self.assertEqual( + last_commit.date, datetime.datetime(2016, 12, 3, 15, 28, 15, tzinfo=dateutil.tz.tzoffset("+0100", 3600)) + ) self.assertListEqual(last_commit.parents, ["åbc"]) self.assertFalse(last_commit.is_merge_commit) self.assertFalse(last_commit.is_fixup_commit) + self.assertFalse(last_commit.is_fixup_amend_commit) self.assertFalse(last_commit.is_squash_commit) self.assertFalse(last_commit.is_revert_commit) # First 2 'git log' calls should've happened at this point self.assertListEqual(sh.git.mock_calls, expected_calls[:3]) - self.assertListEqual(last_commit.changed_files, ["file1.txt", "påth/to/file2.txt"]) + self.assertListEqual(last_commit.changed_files, ["file1.txt", "påth/to/file2.bin"]) + expected_file_stats = { + "file1.txt": GitChangedFileStats("file1.txt", 4, 15), + "påth/to/file2.bin": GitChangedFileStats("påth/to/file2.bin", None, None), + } + self.assertDictEqual(last_commit.changed_files_stats, expected_file_stats) + # 'git diff-tree' should have happened at this point self.assertListEqual(sh.git.mock_calls, expected_calls[:4]) @@ -74,18 +93,17 @@ class GitCommitTests(BaseTestCase): # All expected calls should've happened at this point self.assertListEqual(sh.git.mock_calls, expected_calls) - @patch('gitlint.git.sh') + @patch("gitlint.git.sh") def test_from_local_repository_specific_refspec(self, sh): sample_refspec = "åbc123..def456" sample_sha = "åbc123" sh.git.side_effect = [ sample_sha, # git rev-list - "test åuthor\x00test-emåil@foo.com\x002016-12-03 15:28:15 +0100\x00åbc\n" - "cömmit-title\n\ncömmit-body", + "test åuthor\x00test-emåil@foo.com\x002016-12-03 15:28:15 +0100\x00åbc\ncömmit-title\n\ncömmit-body", "#", # git config --get core.commentchar - "file1.txt\npåth/to/file2.txt\n", - "foöbar\n* hürdur\n" + "7\t10\tfile1.txt\n9\t12\tpåth/to/file2.txt\n", + "foöbar\n* hürdur\n", ] context = GitContext.from_local_repository("fåke/path", refspec=sample_refspec) @@ -93,10 +111,17 @@ class GitCommitTests(BaseTestCase): expected_calls = [ call("rev-list", sample_refspec, **self.expected_sh_special_args), call("log", sample_sha, "-1", "--pretty=%aN%x00%aE%x00%ai%x00%P%n%B", **self.expected_sh_special_args), - call('config', '--get', 'core.commentchar', _ok_code=[0, 1], **self.expected_sh_special_args), - call('diff-tree', '--no-commit-id', '--name-only', '-r', '--root', sample_sha, - **self.expected_sh_special_args), - call('branch', '--contains', sample_sha, **self.expected_sh_special_args) + call("config", "--get", "core.commentchar", _ok_code=[0, 1], **self.expected_sh_special_args), + call( + "diff-tree", + "--no-commit-id", + "--numstat", + "-r", + "--root", + sample_sha, + **self.expected_sh_special_args, + ), + call("branch", "--contains", sample_sha, **self.expected_sh_special_args), ] # Only first 'git log' call should've happened at this point @@ -109,11 +134,13 @@ class GitCommitTests(BaseTestCase): self.assertEqual(last_commit.message.body, ["", "cömmit-body"]) self.assertEqual(last_commit.author_name, "test åuthor") self.assertEqual(last_commit.author_email, "test-emåil@foo.com") - self.assertEqual(last_commit.date, datetime.datetime(2016, 12, 3, 15, 28, 15, - tzinfo=dateutil.tz.tzoffset("+0100", 3600))) + self.assertEqual( + last_commit.date, datetime.datetime(2016, 12, 3, 15, 28, 15, tzinfo=dateutil.tz.tzoffset("+0100", 3600)) + ) self.assertListEqual(last_commit.parents, ["åbc"]) self.assertFalse(last_commit.is_merge_commit) self.assertFalse(last_commit.is_fixup_commit) + self.assertFalse(last_commit.is_fixup_amend_commit) self.assertFalse(last_commit.is_squash_commit) self.assertFalse(last_commit.is_revert_commit) @@ -121,6 +148,12 @@ class GitCommitTests(BaseTestCase): self.assertListEqual(sh.git.mock_calls, expected_calls[:3]) self.assertListEqual(last_commit.changed_files, ["file1.txt", "påth/to/file2.txt"]) + expected_file_stats = { + "file1.txt": GitChangedFileStats("file1.txt", 7, 10), + "påth/to/file2.txt": GitChangedFileStats("påth/to/file2.txt", 9, 12), + } + self.assertDictEqual(last_commit.changed_files_stats, expected_file_stats) + # 'git diff-tree' should have happened at this point self.assertListEqual(sh.git.mock_calls, expected_calls[:4]) @@ -128,28 +161,34 @@ class GitCommitTests(BaseTestCase): # All expected calls should've happened at this point self.assertListEqual(sh.git.mock_calls, expected_calls) - @patch('gitlint.git.sh') + @patch("gitlint.git.sh") def test_from_local_repository_specific_commit_hash(self, sh): sample_hash = "åbc123" sh.git.side_effect = [ sample_hash, # git log -1 - "test åuthor\x00test-emåil@foo.com\x002016-12-03 15:28:15 +0100\x00åbc\n" - "cömmit-title\n\ncömmit-body", + "test åuthor\x00test-emåil@foo.com\x002016-12-03 15:28:15 +0100\x00åbc\ncömmit-title\n\ncömmit-body", "#", # git config --get core.commentchar - "file1.txt\npåth/to/file2.txt\n", - "foöbar\n* hürdur\n" + "8\t3\tfile1.txt\n1\t4\tpåth/to/file2.txt\n", + "foöbar\n* hürdur\n", ] - context = GitContext.from_local_repository("fåke/path", commit_hash=sample_hash) + context = GitContext.from_local_repository("fåke/path", commit_hashes=[sample_hash]) # assert that commit info was read using git command expected_calls = [ - call("log", "-1", sample_hash, "--pretty=%H", **self.expected_sh_special_args), + call("log", "-1", sample_hash, "--pretty=%H", **self.expected_sh_special_args), call("log", sample_hash, "-1", "--pretty=%aN%x00%aE%x00%ai%x00%P%n%B", **self.expected_sh_special_args), - call('config', '--get', 'core.commentchar', _ok_code=[0, 1], **self.expected_sh_special_args), - call('diff-tree', '--no-commit-id', '--name-only', '-r', '--root', sample_hash, - **self.expected_sh_special_args), - call('branch', '--contains', sample_hash, **self.expected_sh_special_args) + call("config", "--get", "core.commentchar", _ok_code=[0, 1], **self.expected_sh_special_args), + call( + "diff-tree", + "--no-commit-id", + "--numstat", + "-r", + "--root", + sample_hash, + **self.expected_sh_special_args, + ), + call("branch", "--contains", sample_hash, **self.expected_sh_special_args), ] # Only first 'git log' call should've happened at this point @@ -162,11 +201,13 @@ class GitCommitTests(BaseTestCase): self.assertEqual(last_commit.message.body, ["", "cömmit-body"]) self.assertEqual(last_commit.author_name, "test åuthor") self.assertEqual(last_commit.author_email, "test-emåil@foo.com") - self.assertEqual(last_commit.date, datetime.datetime(2016, 12, 3, 15, 28, 15, - tzinfo=dateutil.tz.tzoffset("+0100", 3600))) + self.assertEqual( + last_commit.date, datetime.datetime(2016, 12, 3, 15, 28, 15, tzinfo=dateutil.tz.tzoffset("+0100", 3600)) + ) self.assertListEqual(last_commit.parents, ["åbc"]) self.assertFalse(last_commit.is_merge_commit) self.assertFalse(last_commit.is_fixup_commit) + self.assertFalse(last_commit.is_fixup_amend_commit) self.assertFalse(last_commit.is_squash_commit) self.assertFalse(last_commit.is_revert_commit) @@ -174,6 +215,11 @@ class GitCommitTests(BaseTestCase): self.assertListEqual(sh.git.mock_calls, expected_calls[:3]) self.assertListEqual(last_commit.changed_files, ["file1.txt", "påth/to/file2.txt"]) + expected_file_stats = { + "file1.txt": GitChangedFileStats("file1.txt", 8, 3), + "påth/to/file2.txt": GitChangedFileStats("påth/to/file2.txt", 1, 4), + } + self.assertDictEqual(last_commit.changed_files_stats, expected_file_stats) # 'git diff-tree' should have happened at this point self.assertListEqual(sh.git.mock_calls, expected_calls[:4]) @@ -181,17 +227,103 @@ class GitCommitTests(BaseTestCase): # All expected calls should've happened at this point self.assertListEqual(sh.git.mock_calls, expected_calls) - @patch('gitlint.git.sh') + @patch("gitlint.git.sh") + def test_from_local_repository_multiple_commit_hashes(self, sh): + hashes = ["åbc123", "dęf456", "ghí789"] + sh.git.side_effect = [ + *hashes, + f"test åuthor {hashes[0]}\x00test-emåil-{hashes[0]}@foo.com\x002016-12-03 15:28:15 +0100\x00åbc\n" + f"cömmit-title {hashes[0]}\n\ncömmit-body {hashes[0]}", + "#", # git config --get core.commentchar + f"test åuthor {hashes[1]}\x00test-emåil-{hashes[1]}@foo.com\x002016-12-03 15:28:15 +0100\x00åbc\n" + f"cömmit-title {hashes[1]}\n\ncömmit-body {hashes[1]}", + f"test åuthor {hashes[2]}\x00test-emåil-{hashes[2]}@foo.com\x002016-12-03 15:28:15 +0100\x00åbc\n" + f"cömmit-title {hashes[2]}\n\ncömmit-body {hashes[2]}", + f"2\t5\tfile1-{hashes[0]}.txt\n7\t1\tpåth/to/file2.txt\n", + f"2\t5\tfile1-{hashes[1]}.txt\n7\t1\tpåth/to/file2.txt\n", + f"2\t5\tfile1-{hashes[2]}.txt\n7\t1\tpåth/to/file2.txt\n", + f"foöbar-{hashes[0]}\n* hürdur\n", + f"foöbar-{hashes[1]}\n* hürdur\n", + f"foöbar-{hashes[2]}\n* hürdur\n", + ] + + expected_calls = [ + call("log", "-1", hashes[0], "--pretty=%H", **self.expected_sh_special_args), + call("log", "-1", hashes[1], "--pretty=%H", **self.expected_sh_special_args), + call("log", "-1", hashes[2], "--pretty=%H", **self.expected_sh_special_args), + call("log", hashes[0], "-1", "--pretty=%aN%x00%aE%x00%ai%x00%P%n%B", **self.expected_sh_special_args), + call("config", "--get", "core.commentchar", _ok_code=[0, 1], **self.expected_sh_special_args), + call("log", hashes[1], "-1", "--pretty=%aN%x00%aE%x00%ai%x00%P%n%B", **self.expected_sh_special_args), + call("log", hashes[2], "-1", "--pretty=%aN%x00%aE%x00%ai%x00%P%n%B", **self.expected_sh_special_args), + call( + "diff-tree", "--no-commit-id", "--numstat", "-r", "--root", hashes[0], **self.expected_sh_special_args + ), + call( + "diff-tree", "--no-commit-id", "--numstat", "-r", "--root", hashes[1], **self.expected_sh_special_args + ), + call( + "diff-tree", "--no-commit-id", "--numstat", "-r", "--root", hashes[2], **self.expected_sh_special_args + ), + call("branch", "--contains", hashes[0], **self.expected_sh_special_args), + call("branch", "--contains", hashes[1], **self.expected_sh_special_args), + call("branch", "--contains", hashes[2], **self.expected_sh_special_args), + ] + + context = GitContext.from_local_repository("fåke/path", commit_hashes=hashes) + + # Only first set of 'git log' calls should've happened at this point + self.assertEqual(sh.git.mock_calls, expected_calls[:3]) + + for i, commit in enumerate(context.commits): + expected_hash = hashes[i] + self.assertIsInstance(commit, LocalGitCommit) + self.assertEqual(commit.sha, expected_hash) + self.assertEqual(commit.message.title, f"cömmit-title {expected_hash}") + self.assertEqual(commit.message.body, ["", f"cömmit-body {expected_hash}"]) + self.assertEqual(commit.author_name, f"test åuthor {expected_hash}") + self.assertEqual(commit.author_email, f"test-emåil-{expected_hash}@foo.com") + self.assertEqual( + commit.date, datetime.datetime(2016, 12, 3, 15, 28, 15, tzinfo=dateutil.tz.tzoffset("+0100", 3600)) + ) + self.assertListEqual(commit.parents, ["åbc"]) + self.assertFalse(commit.is_merge_commit) + self.assertFalse(commit.is_fixup_commit) + self.assertFalse(commit.is_fixup_amend_commit) + self.assertFalse(commit.is_squash_commit) + self.assertFalse(commit.is_revert_commit) + + # All 'git log' calls should've happened at this point + self.assertListEqual(sh.git.mock_calls, expected_calls[:7]) + + for i, commit in enumerate(context.commits): + expected_hash = hashes[i] + self.assertListEqual(commit.changed_files, [f"file1-{expected_hash}.txt", "påth/to/file2.txt"]) + expected_file_stats = { + f"file1-{expected_hash}.txt": GitChangedFileStats(f"file1-{expected_hash}.txt", 2, 5), + "påth/to/file2.txt": GitChangedFileStats("påth/to/file2.txt", 7, 1), + } + self.assertDictEqual(commit.changed_files_stats, expected_file_stats) + + # 'git diff-tree' should have happened at this point + self.assertListEqual(sh.git.mock_calls, expected_calls[:10]) + + for i, commit in enumerate(context.commits): + expected_hash = hashes[i] + self.assertListEqual(commit.branches, [f"foöbar-{expected_hash}", "hürdur"]) + + # All expected calls should've happened at this point + self.assertListEqual(sh.git.mock_calls, expected_calls) + + @patch("gitlint.git.sh") def test_get_latest_commit_merge_commit(self, sh): sample_sha = "d8ac47e9f2923c7f22d8668e3a1ed04eb4cdbca9" sh.git.side_effect = [ sample_sha, - "test åuthor\x00test-emåil@foo.com\x002016-12-03 15:28:15 +0100\x00åbc def\n" - "Merge \"foo bår commit\"", + 'test åuthor\x00test-emåil@foo.com\x002016-12-03 15:28:15 +0100\x00åbc def\nMerge "foo bår commit"', "#", # git config --get core.commentchar - "file1.txt\npåth/to/file2.txt\n", - "foöbar\n* hürdur\n" + "6\t2\tfile1.txt\n1\t4\tpåth/to/file2.txt\n", + "foöbar\n* hürdur\n", ] context = GitContext.from_local_repository("fåke/path") @@ -199,10 +331,17 @@ class GitCommitTests(BaseTestCase): expected_calls = [ call("log", "-1", "--pretty=%H", **self.expected_sh_special_args), call("log", sample_sha, "-1", "--pretty=%aN%x00%aE%x00%ai%x00%P%n%B", **self.expected_sh_special_args), - call('config', '--get', 'core.commentchar', _ok_code=[0, 1], **self.expected_sh_special_args), - call('diff-tree', '--no-commit-id', '--name-only', '-r', '--root', sample_sha, - **self.expected_sh_special_args), - call('branch', '--contains', sample_sha, **self.expected_sh_special_args) + call("config", "--get", "core.commentchar", _ok_code=[0, 1], **self.expected_sh_special_args), + call( + "diff-tree", + "--no-commit-id", + "--numstat", + "-r", + "--root", + sample_sha, + **self.expected_sh_special_args, + ), + call("branch", "--contains", sample_sha, **self.expected_sh_special_args), ] # Only first 'git log' call should've happened at this point @@ -211,15 +350,17 @@ class GitCommitTests(BaseTestCase): last_commit = context.commits[-1] self.assertIsInstance(last_commit, LocalGitCommit) self.assertEqual(last_commit.sha, sample_sha) - self.assertEqual(last_commit.message.title, "Merge \"foo bår commit\"") + self.assertEqual(last_commit.message.title, 'Merge "foo bår commit"') self.assertEqual(last_commit.message.body, []) self.assertEqual(last_commit.author_name, "test åuthor") self.assertEqual(last_commit.author_email, "test-emåil@foo.com") - self.assertEqual(last_commit.date, datetime.datetime(2016, 12, 3, 15, 28, 15, - tzinfo=dateutil.tz.tzoffset("+0100", 3600))) + self.assertEqual( + last_commit.date, datetime.datetime(2016, 12, 3, 15, 28, 15, tzinfo=dateutil.tz.tzoffset("+0100", 3600)) + ) self.assertListEqual(last_commit.parents, ["åbc", "def"]) self.assertTrue(last_commit.is_merge_commit) self.assertFalse(last_commit.is_fixup_commit) + self.assertFalse(last_commit.is_fixup_amend_commit) self.assertFalse(last_commit.is_squash_commit) self.assertFalse(last_commit.is_revert_commit) @@ -227,6 +368,11 @@ class GitCommitTests(BaseTestCase): self.assertListEqual(sh.git.mock_calls, expected_calls[:3]) self.assertListEqual(last_commit.changed_files, ["file1.txt", "påth/to/file2.txt"]) + expected_file_stats = { + "file1.txt": GitChangedFileStats("file1.txt", 6, 2), + "påth/to/file2.txt": GitChangedFileStats("påth/to/file2.txt", 1, 4), + } + self.assertDictEqual(last_commit.changed_files_stats, expected_file_stats) # 'git diff-tree' should have happened at this point self.assertListEqual(sh.git.mock_calls, expected_calls[:4]) @@ -234,19 +380,19 @@ class GitCommitTests(BaseTestCase): # All expected calls should've happened at this point self.assertListEqual(sh.git.mock_calls, expected_calls) - @patch('gitlint.git.sh') + @patch("gitlint.git.sh") def test_get_latest_commit_fixup_squash_commit(self, sh): - commit_types = ["fixup", "squash"] - for commit_type in commit_types: + commit_prefixes = {"fixup": "is_fixup_commit", "squash": "is_squash_commit", "amend": "is_fixup_amend_commit"} + for commit_type in commit_prefixes.keys(): sample_sha = "d8ac47e9f2923c7f22d8668e3a1ed04eb4cdbca9" sh.git.side_effect = [ sample_sha, "test åuthor\x00test-emåil@foo.com\x002016-12-03 15:28:15 +0100\x00åbc\n" - f"{commit_type}! \"foo bår commit\"", + f'{commit_type}! "foo bår commit"', "#", # git config --get core.commentchar - "file1.txt\npåth/to/file2.txt\n", - "foöbar\n* hürdur\n" + "8\t2\tfile1.txt\n7\t3\tpåth/to/file2.txt\n", + "foöbar\n* hürdur\n", ] context = GitContext.from_local_repository("fåke/path") @@ -254,10 +400,17 @@ class GitCommitTests(BaseTestCase): expected_calls = [ call("log", "-1", "--pretty=%H", **self.expected_sh_special_args), call("log", sample_sha, "-1", "--pretty=%aN%x00%aE%x00%ai%x00%P%n%B", **self.expected_sh_special_args), - call('config', '--get', 'core.commentchar', _ok_code=[0, 1], **self.expected_sh_special_args), - call('diff-tree', '--no-commit-id', '--name-only', '-r', '--root', sample_sha, - **self.expected_sh_special_args), - call('branch', '--contains', sample_sha, **self.expected_sh_special_args) + call("config", "--get", "core.commentchar", _ok_code=[0, 1], **self.expected_sh_special_args), + call( + "diff-tree", + "--no-commit-id", + "--numstat", + "-r", + "--root", + sample_sha, + **self.expected_sh_special_args, + ), + call("branch", "--contains", sample_sha, **self.expected_sh_special_args), ] # Only first 'git log' call should've happened at this point @@ -266,27 +419,31 @@ class GitCommitTests(BaseTestCase): last_commit = context.commits[-1] self.assertIsInstance(last_commit, LocalGitCommit) self.assertEqual(last_commit.sha, sample_sha) - self.assertEqual(last_commit.message.title, f"{commit_type}! \"foo bår commit\"") + self.assertEqual(last_commit.message.title, f'{commit_type}! "foo bår commit"') self.assertEqual(last_commit.message.body, []) self.assertEqual(last_commit.author_name, "test åuthor") self.assertEqual(last_commit.author_email, "test-emåil@foo.com") - self.assertEqual(last_commit.date, datetime.datetime(2016, 12, 3, 15, 28, 15, - tzinfo=dateutil.tz.tzoffset("+0100", 3600))) + self.assertEqual( + last_commit.date, datetime.datetime(2016, 12, 3, 15, 28, 15, tzinfo=dateutil.tz.tzoffset("+0100", 3600)) + ) self.assertListEqual(last_commit.parents, ["åbc"]) # First 2 'git log' calls should've happened at this point self.assertEqual(sh.git.mock_calls, expected_calls[:3]) # Asserting that squash and fixup are correct - for type in commit_types: - attr = "is_" + type + "_commit" + for type, attr in commit_prefixes.items(): self.assertEqual(getattr(last_commit, attr), commit_type == type) self.assertFalse(last_commit.is_merge_commit) self.assertFalse(last_commit.is_revert_commit) self.assertListEqual(last_commit.changed_files, ["file1.txt", "påth/to/file2.txt"]) + expected_file_stats = { + "file1.txt": GitChangedFileStats("file1.txt", 8, 2), + "påth/to/file2.txt": GitChangedFileStats("påth/to/file2.txt", 7, 3), + } + self.assertDictEqual(last_commit.changed_files_stats, expected_file_stats) - self.assertListEqual(last_commit.changed_files, ["file1.txt", "påth/to/file2.txt"]) # 'git diff-tree' should have happened at this point self.assertListEqual(sh.git.mock_calls, expected_calls[:4]) @@ -302,14 +459,16 @@ class GitCommitTests(BaseTestCase): gitcontext = GitContext.from_commit_msg(self.get_sample("commit_message/sample1")) expected_title = "Commit title contåining 'WIP', as well as trailing punctuation." - expected_body = ["This line should be empty", - "This is the first line of the commit message body and it is meant to test a " + - "line that exceeds the maximum line length of 80 characters.", - "This line has a tråiling space. ", - "This line has a trailing tab.\t"] + expected_body = [ + "This line should be empty", + "This is the first line of the commit message body and it is meant to test a " + + "line that exceeds the maximum line length of 80 characters.", + "This line has a tråiling space. ", + "This line has a trailing tab.\t", + ] expected_full = expected_title + "\n" + "\n".join(expected_body) - expected_original = expected_full + ( - "\n# This is a cömmented line\n" + expected_original = ( + expected_full + "\n# This is a cömmented line\n" "# ------------------------ >8 ------------------------\n" "# Anything after this line should be cleaned up\n" "# this line appears on `git commit -v` command\n" @@ -335,6 +494,7 @@ class GitCommitTests(BaseTestCase): self.assertListEqual(commit.branches, []) self.assertFalse(commit.is_merge_commit) self.assertFalse(commit.is_fixup_commit) + self.assertFalse(commit.is_fixup_amend_commit) self.assertFalse(commit.is_squash_commit) self.assertFalse(commit.is_revert_commit) self.assertEqual(len(gitcontext.commits), 1) @@ -355,6 +515,7 @@ class GitCommitTests(BaseTestCase): self.assertListEqual(commit.branches, []) self.assertFalse(commit.is_merge_commit) self.assertFalse(commit.is_fixup_commit) + self.assertFalse(commit.is_fixup_amend_commit) self.assertFalse(commit.is_squash_commit) self.assertFalse(commit.is_revert_commit) self.assertEqual(len(gitcontext.commits), 1) @@ -376,6 +537,7 @@ class GitCommitTests(BaseTestCase): self.assertListEqual(commit.branches, []) self.assertFalse(commit.is_merge_commit) self.assertFalse(commit.is_fixup_commit) + self.assertFalse(commit.is_fixup_amend_commit) self.assertFalse(commit.is_squash_commit) self.assertFalse(commit.is_revert_commit) self.assertEqual(len(gitcontext.commits), 1) @@ -400,6 +562,7 @@ class GitCommitTests(BaseTestCase): self.assertFalse(commit.is_merge_commit) self.assertFalse(commit.is_fixup_commit) self.assertFalse(commit.is_squash_commit) + self.assertFalse(commit.is_fixup_amend_commit) self.assertFalse(commit.is_revert_commit) self.assertEqual(len(gitcontext.commits), 1) @@ -421,18 +584,19 @@ class GitCommitTests(BaseTestCase): self.assertListEqual(commit.branches, []) self.assertTrue(commit.is_merge_commit) self.assertFalse(commit.is_fixup_commit) + self.assertFalse(commit.is_fixup_amend_commit) self.assertFalse(commit.is_squash_commit) self.assertFalse(commit.is_revert_commit) self.assertEqual(len(gitcontext.commits), 1) def test_from_commit_msg_revert_commit(self): - commit_msg = "Revert \"Prev commit message\"\n\nThis reverts commit a8ad67e04164a537198dea94a4fde81c5592ae9c." + commit_msg = 'Revert "Prev commit message"\n\nThis reverts commit a8ad67e04164a537198dea94a4fde81c5592ae9c.' gitcontext = GitContext.from_commit_msg(commit_msg) commit = gitcontext.commits[-1] self.assertIsInstance(commit, GitCommit) self.assertFalse(isinstance(commit, LocalGitCommit)) - self.assertEqual(commit.message.title, "Revert \"Prev commit message\"") + self.assertEqual(commit.message.title, 'Revert "Prev commit message"') self.assertEqual(commit.message.body, ["", "This reverts commit a8ad67e04164a537198dea94a4fde81c5592ae9c."]) self.assertEqual(commit.message.full, commit_msg) self.assertEqual(commit.message.original, commit_msg) @@ -443,13 +607,16 @@ class GitCommitTests(BaseTestCase): self.assertListEqual(commit.branches, []) self.assertFalse(commit.is_merge_commit) self.assertFalse(commit.is_fixup_commit) + self.assertFalse(commit.is_fixup_amend_commit) self.assertFalse(commit.is_squash_commit) self.assertTrue(commit.is_revert_commit) self.assertEqual(len(gitcontext.commits), 1) - def test_from_commit_msg_fixup_squash_commit(self): - commit_types = ["fixup", "squash"] - for commit_type in commit_types: + def test_from_commit_msg_fixup_squash_amend_commit(self): + # mapping between cleanup commit prefixes and the commit object attribute + commit_prefixes = {"fixup": "is_fixup_commit", "squash": "is_squash_commit", "amend": "is_fixup_amend_commit"} + + for commit_type in commit_prefixes.keys(): commit_msg = f"{commit_type}! Test message" gitcontext = GitContext.from_commit_msg(commit_msg) commit = gitcontext.commits[-1] @@ -469,34 +636,33 @@ class GitCommitTests(BaseTestCase): self.assertFalse(commit.is_merge_commit) self.assertFalse(commit.is_revert_commit) # Asserting that squash and fixup are correct - for type in commit_types: - attr = "is_" + type + "_commit" - self.assertEqual(getattr(commit, attr), commit_type == type) + for type, commit_attr_name in commit_prefixes.items(): + self.assertEqual(getattr(commit, commit_attr_name), commit_type == type) - @patch('gitlint.git.sh') - @patch('arrow.now') + @patch("gitlint.git.sh") + @patch("arrow.now") def test_staged_commit(self, now, sh): # StagedLocalGitCommit() sh.git.side_effect = [ - "#", # git config --get core.commentchar - "test åuthor\n", # git config --get user.name - "test-emåil@foo.com\n", # git config --get user.email - "my-brånch\n", # git rev-parse --abbrev-ref HEAD - "file1.txt\npåth/to/file2.txt\n", + "#", # git config --get core.commentchar + "test åuthor\n", # git config --get user.name + "test-emåil@foo.com\n", # git config --get user.email + "my-brånch\n", # git rev-parse --abbrev-ref HEAD + "4\t2\tfile1.txt\n13\t9\tpåth/to/file2.txt\n", ] now.side_effect = [arrow.get("2020-02-19T12:18:46.675182+01:00")] # We use a fixup commit, just to test a non-default path context = GitContext.from_staged_commit("fixup! Foōbar 123\n\ncömmit-body\n", "fåke/path") - # git calls we're expexting + # git calls we're expecting expected_calls = [ - call('config', '--get', 'core.commentchar', _ok_code=[0, 1], **self.expected_sh_special_args), - call('config', '--get', 'user.name', **self.expected_sh_special_args), - call('config', '--get', 'user.email', **self.expected_sh_special_args), + call("config", "--get", "core.commentchar", _ok_code=[0, 1], **self.expected_sh_special_args), + call("config", "--get", "user.name", **self.expected_sh_special_args), + call("config", "--get", "user.email", **self.expected_sh_special_args), call("rev-parse", "--abbrev-ref", "HEAD", **self.expected_sh_special_args), - call("diff", "--staged", "--name-only", "-r", **self.expected_sh_special_args) + call("diff", "--staged", "--numstat", "-r", **self.expected_sh_special_args), ] last_commit = context.commits[-1] @@ -513,13 +679,15 @@ class GitCommitTests(BaseTestCase): self.assertEqual(last_commit.author_email, "test-emåil@foo.com") self.assertListEqual(sh.git.mock_calls, expected_calls[0:3]) - self.assertEqual(last_commit.date, datetime.datetime(2020, 2, 19, 12, 18, 46, - tzinfo=dateutil.tz.tzoffset("+0100", 3600))) + self.assertEqual( + last_commit.date, datetime.datetime(2020, 2, 19, 12, 18, 46, tzinfo=dateutil.tz.tzoffset("+0100", 3600)) + ) now.assert_called_once() self.assertListEqual(last_commit.parents, []) self.assertFalse(last_commit.is_merge_commit) self.assertTrue(last_commit.is_fixup_commit) + self.assertFalse(last_commit.is_fixup_amend_commit) self.assertFalse(last_commit.is_squash_commit) self.assertFalse(last_commit.is_revert_commit) @@ -527,42 +695,48 @@ class GitCommitTests(BaseTestCase): self.assertListEqual(sh.git.mock_calls, expected_calls[0:4]) self.assertListEqual(last_commit.changed_files, ["file1.txt", "påth/to/file2.txt"]) + expected_file_stats = { + "file1.txt": GitChangedFileStats("file1.txt", 4, 2), + "påth/to/file2.txt": GitChangedFileStats("påth/to/file2.txt", 13, 9), + } + self.assertDictEqual(last_commit.changed_files_stats, expected_file_stats) + self.assertListEqual(sh.git.mock_calls, expected_calls[0:5]) - @patch('gitlint.git.sh') + @patch("gitlint.git.sh") def test_staged_commit_with_missing_username(self, sh): - # StagedLocalGitCommit() - sh.git.side_effect = [ - "#", # git config --get core.commentchar - ErrorReturnCode('git config --get user.name', b"", b""), + "#", # git config --get core.commentchar + ErrorReturnCode("git config --get user.name", b"", b""), ] expected_msg = "Missing git configuration: please set user.name" with self.assertRaisesMessage(GitContextError, expected_msg): ctx = GitContext.from_staged_commit("Foōbar 123\n\ncömmit-body\n", "fåke/path") - [str(commit) for commit in ctx.commits] + ctx.commits[0].author_name # accessing this attribute should raise an exception - @patch('gitlint.git.sh') + @patch("gitlint.git.sh") def test_staged_commit_with_missing_email(self, sh): - # StagedLocalGitCommit() - sh.git.side_effect = [ - "#", # git config --get core.commentchar - "test åuthor\n", # git config --get user.name - ErrorReturnCode('git config --get user.name', b"", b""), + "#", # git config --get core.commentchar + ErrorReturnCode("git config --get user.email", b"", b""), ] expected_msg = "Missing git configuration: please set user.email" with self.assertRaisesMessage(GitContextError, expected_msg): ctx = GitContext.from_staged_commit("Foōbar 123\n\ncömmit-body\n", "fåke/path") - [str(commit) for commit in ctx.commits] + ctx.commits[0].author_email # accessing this attribute should raise an exception def test_gitcommitmessage_equality(self): commit_message1 = GitCommitMessage(GitContext(), "tëst\n\nfoo", "tëst\n\nfoo", "tēst", ["", "föo"]) - attrs = ['original', 'full', 'title', 'body'] + attrs = ["original", "full", "title", "body"] self.object_equality_test(commit_message1, attrs, {"context": commit_message1.context}) + def test_gitchangedfilestats_equality(self): + changed_file_stats = GitChangedFileStats(Path("foö/bar"), 5, 13) + attrs = ["filepath", "additions", "deletions"] + self.object_equality_test(changed_file_stats, attrs) + @patch("gitlint.git._git") def test_gitcommit_equality(self, git): # git will be called to setup the context (commentchar and current_branch), just return the same value @@ -573,14 +747,32 @@ class GitCommitTests(BaseTestCase): now = datetime.datetime.utcnow() context1 = GitContext() commit_message1 = GitCommitMessage(context1, "tëst\n\nfoo", "tëst\n\nfoo", "tēst", ["", "föo"]) - commit1 = GitCommit(context1, commit_message1, "shä", now, "Jöhn Smith", "jöhn.smith@test.com", None, - ["föo/bar"], ["brånch1", "brånch2"]) + commit1 = GitCommit( + context1, + commit_message1, + "shä", + now, + "Jöhn Smith", + "jöhn.smith@test.com", + None, + {"föo/bar": GitChangedFileStats("föo/bar", 5, 13)}, + ["brånch1", "brånch2"], + ) context1.commits = [commit1] context2 = GitContext() commit_message2 = GitCommitMessage(context2, "tëst\n\nfoo", "tëst\n\nfoo", "tēst", ["", "föo"]) - commit2 = GitCommit(context2, commit_message1, "shä", now, "Jöhn Smith", "jöhn.smith@test.com", None, - ["föo/bar"], ["brånch1", "brånch2"]) + commit2 = GitCommit( + context2, + commit_message1, + "shä", + now, + "Jöhn Smith", + "jöhn.smith@test.com", + None, + {"föo/bar": GitChangedFileStats("föo/bar", 5, 13)}, + ["brånch1", "brånch2"], + ) context2.commits = [commit2] self.assertEqual(context1, context2) @@ -588,15 +780,29 @@ class GitCommitTests(BaseTestCase): self.assertEqual(commit1, commit2) # Check that objects are unequal when changing a single attribute - kwargs = {'message': commit1.message, 'sha': commit1.sha, 'date': commit1.date, - 'author_name': commit1.author_name, 'author_email': commit1.author_email, 'parents': commit1.parents, - 'changed_files': commit1.changed_files, 'branches': commit1.branches} - - self.object_equality_test(commit1, kwargs.keys(), {"context": commit1.context}) + kwargs = { + "message": commit1.message, + "sha": commit1.sha, + "date": commit1.date, + "author_name": commit1.author_name, + "author_email": commit1.author_email, + "parents": commit1.parents, + "branches": commit1.branches, + } + + self.object_equality_test( + commit1, + kwargs.keys(), + {"context": commit1.context, "changed_files_stats": {"föo/bar": GitChangedFileStats("föo/bar", 5, 13)}}, + ) # Check that the is_* attributes that are affected by the commit message affect equality - special_messages = {'is_merge_commit': "Merge: foöbar", 'is_fixup_commit': "fixup! foöbar", - 'is_squash_commit': "squash! foöbar", 'is_revert_commit': "Revert: foöbar"} + special_messages = { + "is_merge_commit": "Merge: foöbar", + "is_fixup_commit": "fixup! foöbar", + "is_squash_commit": "squash! foöbar", + "is_revert_commit": "Revert: foöbar", + } for key in special_messages: kwargs_copy = copy.deepcopy(kwargs) clone1 = GitCommit(context=commit1.context, **kwargs_copy) @@ -607,6 +813,10 @@ class GitCommitTests(BaseTestCase): clone2.message = GitCommitMessage.from_full_message(context1, "foöbar") self.assertNotEqual(clone1, clone2) + # Check changed_files and changed_files_stats + commit2.changed_files_stats = {"föo/bar2": GitChangedFileStats("föo/bar2", 5, 13)} + self.assertNotEqual(commit1, commit2) + @patch("gitlint.git.git_commentchar") def test_commit_msg_custom_commentchar(self, patched): patched.return_value = "ä" diff --git a/gitlint-core/gitlint/tests/git/test_git_context.py b/gitlint-core/gitlint/tests/git/test_git_context.py index bb05236..3dcbe4a 100644 --- a/gitlint-core/gitlint/tests/git/test_git_context.py +++ b/gitlint-core/gitlint/tests/git/test_git_context.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - from unittest.mock import patch, call from gitlint.tests.base import BaseTestCase @@ -7,24 +5,16 @@ from gitlint.git import GitContext class GitContextTests(BaseTestCase): - # Expected special_args passed to 'sh' - expected_sh_special_args = { - '_tty_out': False, - '_cwd': "fåke/path" - } + expected_sh_special_args = {"_tty_out": False, "_cwd": "fåke/path"} - @patch('gitlint.git.sh') + @patch("gitlint.git.sh") def test_gitcontext(self, sh): - - sh.git.side_effect = [ - "#", # git config --get core.commentchar - "\nfoöbar\n" - ] + sh.git.side_effect = ["#", "\nfoöbar\n"] # git config --get core.commentchar expected_calls = [ call("config", "--get", "core.commentchar", _ok_code=[0, 1], **self.expected_sh_special_args), - call("rev-parse", "--abbrev-ref", "HEAD", **self.expected_sh_special_args) + call("rev-parse", "--abbrev-ref", "HEAD", **self.expected_sh_special_args), ] context = GitContext("fåke/path") @@ -38,12 +28,11 @@ class GitContextTests(BaseTestCase): self.assertEqual(context.current_branch, "foöbar") self.assertEqual(sh.git.mock_calls, expected_calls) - @patch('gitlint.git.sh') + @patch("gitlint.git.sh") def test_gitcontext_equality(self, sh): - sh.git.side_effect = [ - "û\n", # context1: git config --get core.commentchar - "û\n", # context2: git config --get core.commentchar + "û\n", # context1: git config --get core.commentchar + "û\n", # context2: git config --get core.commentchar "my-brånch\n", # context1: git rev-parse --abbrev-ref HEAD "my-brånch\n", # context2: git rev-parse --abbrev-ref HEAD ] @@ -68,17 +57,17 @@ class GitContextTests(BaseTestCase): # Different comment_char context3 = GitContext("fåke/path") context3.commits = ["fōo", "bår"] - sh.git.side_effect = ([ - "ç\n", # context3: git config --get core.commentchar - "my-brånch\n" # context3: git rev-parse --abbrev-ref HEAD - ]) + sh.git.side_effect = [ + "ç\n", # context3: git config --get core.commentchar + "my-brånch\n", # context3: git rev-parse --abbrev-ref HEAD + ] self.assertNotEqual(context1, context3) # Different current_branch context4 = GitContext("fåke/path") context4.commits = ["fōo", "bår"] - sh.git.side_effect = ([ - "û\n", # context4: git config --get core.commentchar - "different-brånch\n" # context4: git rev-parse --abbrev-ref HEAD - ]) + sh.git.side_effect = [ + "û\n", # context4: git config --get core.commentchar + "different-brånch\n", # context4: git rev-parse --abbrev-ref HEAD + ] self.assertNotEqual(context1, context4) -- cgit v1.2.3