def test_lint_staged_stdin(self): """ Tests linting a staged commit. Gitint should lint the passed commit message andfetch additional meta-data from the underlying repository. The easiest way to test this is by inspecting `--debug` output. This is the equivalent of doing: echo "WIP: Pïpe test." | gitlint --staged --debug """ # Create a commit first, before we stage changes. This ensures the repo is properly initialized. self.create_simple_commit(u"Sïmple title.\n") # Add some files, stage them: they should show up in the debug output as changed file filename1 = self.create_file(self.tmp_git_repo) git("add", filename1, _cwd=self.tmp_git_repo) filename2 = self.create_file(self.tmp_git_repo) git("add", filename2, _cwd=self.tmp_git_repo) output = gitlint(echo(u"WIP: Pïpe test."), "--staged", "--debug", _cwd=self.tmp_git_repo, _tty_in=False, _err_to_out=True, _ok_code=[3]) # Determine variable parts of expected output expected_kwargs = self.get_debug_vars_last_commit() expected_kwargs.update({'changed_files': sstr(sorted([filename1, filename2]))}) # It's not really possible to determine the "Date: ..." line that is part of the debug output as this date # is not taken from git but instead generated by gitlint itself. As a workaround, we extract the date from the # gitlint output using a regex, parse the date to ensure the format is correct, and then pass that as an # expected variable. matches = re.search(r'^Date:\s+(.*)', str(output), re.MULTILINE) if matches: expected_date = arrow.get(str(matches.group(1)), "YYYY-MM-DD HH:mm:ss Z").format("YYYY-MM-DD HH:mm:ss Z") expected_kwargs['staged_date'] = expected_date self.assertEqualStdout(output, self.get_expected("test_commits/test_lint_staged_stdin_1", expected_kwargs)) self.assertEqual(output.exit_code, 3)
def test_commit_hook_worktree(self): """ Tests that hook installation and un-installation also work in git worktrees. Test steps: ```sh git init <tmpdir> cd <tmpdir> git worktree add <worktree-tempdir> cd <worktree-tempdir> gitlint install-hook gitlint uninstall-hook ``` """ tmp_git_repo = self.create_tmp_git_repo() self.create_simple_commit("Simple title\n\nContënt in the body", git_repo=tmp_git_repo) worktree_dir = self.generate_temp_path() self.tmp_git_repos.append( worktree_dir) # make sure we clean up the worktree afterwards git("worktree", "add", worktree_dir, _cwd=tmp_git_repo, _tty_in=True) output_installed = gitlint("install-hook", _cwd=worktree_dir) expected_hook_path = os.path.join(tmp_git_repo, ".git", "hooks", "commit-msg") expected_msg = f"Successfully installed gitlint commit-msg hook in {expected_hook_path}\r\n" self.assertEqual(output_installed, expected_msg) output_uninstalled = gitlint("uninstall-hook", _cwd=worktree_dir) expected_hook_path = os.path.join(tmp_git_repo, ".git", "hooks", "commit-msg") expected_msg = f"Successfully uninstalled gitlint commit-msg hook from {expected_hook_path}\r\n" self.assertEqual(output_uninstalled, expected_msg)
def test_successful(self): """ Test linting multiple commits without violations """ git("checkout", "-b", "test-branch-commits-base", _cwd=self.tmp_git_repo) self.create_simple_commit(u"Sïmple title\n\nSimple bödy describing the commit") git("checkout", "-b", "test-branch-commits", _cwd=self.tmp_git_repo) self.create_simple_commit(u"Sïmple title2\n\nSimple bödy describing the commit2") self.create_simple_commit(u"Sïmple title3\n\nSimple bödy describing the commit3") output = gitlint("--commits", "test-branch-commits-base...test-branch-commits", _cwd=self.tmp_git_repo, _tty_in=True) self.assertEqualStdout(output, "")
def test_successful_gitconfig(self): """ Test gitlint when the underlying repo has specific git config set. In the past, we've had issues with gitlint failing on some of these, so this acts as a regression test. """ # Different commentchar (Note: tried setting this to a special unicode char, but git doesn't like that) git("config", "--add", "core.commentchar", "$", _cwd=self.tmp_git_repo) self.create_simple_commit( u"Sïmple title\n\nSimple bödy describing the commit\n$after commentchar\t ignored" ) output = gitlint(_cwd=self.tmp_git_repo, _tty_in=True, _err_to_out=True) self.assertEqualStdout(output, "")
def test_commit_hook_continue(self): self.responses = ["y"] test_filename = self._create_simple_commit( u"WIP: This ïs a title.\nContënt on the second line", out=self._interact, tty_in=True) # Determine short commit-msg hash, needed to determine expected output short_hash = git("rev-parse", "--short", "HEAD", _cwd=self.tmp_git_repo, _tty_in=True).replace("\n", "") expected_output = self._violations() expected_output += [ "Continue with commit anyways (this keeps the current commit message)? " + "[y(es)/n(no)/e(dit)] " + u"[master (root-commit) %s] WIP: This ïs a title. Contënt on the second line\n" % short_hash, " 1 file changed, 0 insertions(+), 0 deletions(-)\n", u" create mode 100644 %s\n" % test_filename ] assert len(self.githook_output) == len(expected_output) for output, expected in zip(self.githook_output, expected_output): self.assertMultiLineEqual(output.replace('\r', ''), expected.replace('\r', ''))
def get_system_info_dict(): """ Returns a dict with items related to system values logged by `gitlint --debug` """ expected_gitlint_version = gitlint("--version").replace("gitlint, version ", "").strip() expected_git_version = git("--version").strip() return {'platform': platform.platform(), 'python_version': sys.version, 'git_version': expected_git_version, 'gitlint_version': expected_gitlint_version, 'GITLINT_USE_SH_LIB': BaseTestCase.GITLINT_USE_SH_LIB, 'DEFAULT_ENCODING': DEFAULT_ENCODING}
def get_last_commit_short_hash(self, git_repo=None): git_repo = self.tmp_git_repo if git_repo is None else git_repo return git("rev-parse", "--short", "HEAD", _cwd=git_repo, _err_to_out=True).replace("\n", "")
def test_violations(self): """ Test linting multiple commits with violations """ git("checkout", "-b", "test-branch-commits-violations-base", _cwd=self.tmp_git_repo) self.create_simple_commit(u"Sïmple title.\n") git("checkout", "-b", "test-branch-commits-violations", _cwd=self.tmp_git_repo) self.create_simple_commit(u"Sïmple title2.\n") commit_sha1 = self.get_last_commit_hash()[:10] self.create_simple_commit(u"Sïmple title3.\n") commit_sha2 = self.get_last_commit_hash()[:10] output = gitlint("--commits", "test-branch-commits-violations-base...test-branch-commits-violations", _cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[4]) self.assertEqual(output.exit_code, 4) expected_kwargs = {'commit_sha1': commit_sha1, 'commit_sha2': commit_sha2} self.assertEqualStdout(output, self.get_expected("test_commits/test_violations_1", expected_kwargs))
def test_revert_commit(self): self.create_simple_commit(u"WIP: Cömmit on master.\n\nSimple bödy") hash = self.get_last_commit_hash() git("revert", hash, _cwd=self.tmp_git_repo) # Run gitlint and assert output is empty output = gitlint(_cwd=self.tmp_git_repo, _tty_in=True) self.assertEqualStdout(output, "") # Assert that we do see the error if we disable the ignore-revert-commits option output = gitlint("-c", "general.ignore-revert-commits=false", _cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[1]) self.assertEqual(output.exit_code, 1) expected = u"1: T5 Title contains the word 'WIP' (case-insensitive): \"Revert \"WIP: Cömmit on master.\"\"\n" self.assertEqualStdout(output, expected)
def get_debug_vars_last_commit(self, git_repo=None): """ Returns a dict with items related to `gitlint --debug` output for the last commit. """ target_repo = git_repo if git_repo else self.tmp_git_repo commit_sha = self.get_last_commit_hash(git_repo=target_repo) expected_date = git("log", "-1", "--pretty=%ai", _tty_out=False, _cwd=target_repo) expected_date = arrow.get(str(expected_date), "YYYY-MM-DD HH:mm:ss Z").format("YYYY-MM-DD HH:mm:ss Z") expected_kwargs = self.get_system_info_dict() expected_kwargs.update({'target': target_repo, 'commit_sha': commit_sha, 'commit_date': expected_date}) return expected_kwargs
def test_squash_commit(self): # Create a normal commit and assert that it has a violation test_filename = self.create_simple_commit( u"Cömmit on WIP master\n\nSimple bödy that is long enough") output = gitlint(_cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[1]) expected = u"1: T5 Title contains the word 'WIP' (case-insensitive): \"Cömmit on WIP master\"\n" self.assertEqualStdout(output, expected) # Make a small modification to the commit and commit it using squash commit with io.open(os.path.join(self.tmp_git_repo, test_filename), "a", encoding=DEFAULT_ENCODING) as fh: # Wanted to write a unicode string, but that's obnoxious if you want to do it across Python 2 and 3. # https://stackoverflow.com/questions/22392377/ # error-writing-a-file-with-file-write-in-python-unicodeencodeerror # So just keeping it simple - ASCII will here fh.write(u"Appending some stuff\n") git("add", test_filename, _cwd=self.tmp_git_repo) git("commit", "--squash", self.get_last_commit_hash(), "-m", u"Töo short body", _cwd=self.tmp_git_repo) # Assert that gitlint does not show an error for the fixup commit output = gitlint(_cwd=self.tmp_git_repo, _tty_in=True) # No need to check exit code, the command above throws an exception on > 0 exit codes self.assertEqualStdout(output, "") # Make sure that if we set the ignore-squash-commits option to false that we do still see the violations output = gitlint("-c", "general.ignore-squash-commits=false", _cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[2]) expected = u"1: T5 Title contains the word 'WIP' (case-insensitive): \"squash! Cömmit on WIP master\"\n" + \ u"3: B5 Body message is too short (14<20): \"Töo short body\"\n" self.assertEqualStdout(output, expected)
def create_tmp_git_repo(cls): """ Creates a temporary git repository and returns its directory path """ tmp_git_repo = cls.generate_temp_path() cls.tmp_git_repos.append(tmp_git_repo) git("init", tmp_git_repo) # configuring name and email is required in every git repot git("config", "user.name", "gitlint-test-user", _cwd=tmp_git_repo) git("config", "user.email", "*****@*****.**", _cwd=tmp_git_repo) # Git does not by default print unicode paths, fix that by setting core.quotePath to false # http://stackoverflow.com/questions/34549040/git-not-displaying-unicode-file-names # ftp://www.kernel.org/pub/software/scm/git/docs/git-config.html git("config", "core.quotePath", "false", _cwd=tmp_git_repo) # Git on mac doesn't like unicode characters by default, so we need to set this option # http://stackoverflow.com/questions/5581857/git-and-the-umlaut-problem-on-mac-os-x git("config", "core.precomposeunicode", "true", _cwd=tmp_git_repo) return tmp_git_repo
def _create_simple_commit(self, message, out=None, ok_code=None, env=None, git_repo=None, tty_in=False): """ Creates a simple commit with an empty test file. :param message: Commit message for the commit. """ git_repo = self.tmp_git_repo if git_repo is None else git_repo # Let's make sure that we copy the environment in which this python code was executed as environment # variables can influence how git runs. # This was needed to fix https://github.com/jorisroovers/gitlint/issues/15 as we need to make sure to use # the PATH variable that contains the virtualenv's python binary. environment = os.environ if env: environment.update(env) # Create file and add to git test_filename = u"test-fïle-" + str(uuid4()) io.open(os.path.join(git_repo, test_filename), 'a', encoding=DEFAULT_ENCODING).close() git("add", test_filename, _cwd=git_repo) # https://amoffat.github.io/sh/#interactive-callbacks if not ok_code: ok_code = [0] git("commit", "-m", message, _cwd=git_repo, _err_to_out=True, _out=out, _tty_in=tty_in, _ok_code=ok_code, _env=environment) return test_filename
def test_commit_hook_abort(self): self.responses = ["n"] test_filename = self.create_simple_commit( "WIP: This ïs a title.\nContënt on the second line", out=self._interact, ok_code=1, tty_in=True) git("rm", "-f", test_filename, _cwd=self.tmp_git_repo) # Determine short commit-msg hash, needed to determine expected output expected_output = self._violations() expected_output += [ "Continue with commit anyways (this keeps the current commit message)? " + "[y(es)/n(no)/e(dit)] " + "Commit aborted.\n", "Your commit message: \n", "-----------------------------------------------\n", "WIP: This ïs a title.\n", "Contënt on the second line\n", "-----------------------------------------------\n" ] self.assertListEqual(expected_output, self.githook_output)
def test_lint_head(self): """ Testing whether we can also recognize special refs like 'HEAD' """ tmp_git_repo = self.create_tmp_git_repo() self.create_simple_commit(u"Sïmple title.\n\nSimple bödy describing the commit", git_repo=tmp_git_repo) self.create_simple_commit(u"Sïmple title", git_repo=tmp_git_repo) self.create_simple_commit(u"WIP: Sïmple title\n\nSimple bödy describing the commit", git_repo=tmp_git_repo) output = gitlint("--commits", "HEAD", _cwd=tmp_git_repo, _tty_in=True, _ok_code=[3]) revlist = git("rev-list", "HEAD", _tty_in=True, _cwd=tmp_git_repo).split() expected_kwargs = {"commit_sha0": revlist[0][:10], "commit_sha1": revlist[1][:10], "commit_sha2": revlist[2][:10]} self.assertEqualStdout(output, self.get_expected("test_commits/test_lint_head_1", expected_kwargs))
def test_config_from_file_debug(self): commit_msg = u"WIP: Thïs is a title thåt is a bit longer.\nContent on the second line\n" + \ "This line of the body is here because we need it" self._create_simple_commit(commit_msg) commit_sha = self.get_last_commit_hash() config_path = self.get_sample_path("config/gitlintconfig") output = gitlint("--config", config_path, "--debug", _cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[5]) expected_date = git("log", "-1", "--pretty=%ai", _cwd=self.tmp_git_repo) expected_date = arrow.get(str(expected_date), "YYYY-MM-DD HH:mm:ss Z").datetime expected_gitlint_version = gitlint("--version").replace( "gitlint, version ", "").replace("\n", "") expected_git_version = git("--version").replace("\n", "") expected_kwargs = { 'platform': platform.platform(), 'python_version': sys.version, 'git_version': expected_git_version, 'gitlint_version': expected_gitlint_version, 'GITLINT_USE_SH_LIB': self.GITLINT_USE_SH_LIB, 'config_path': config_path, 'target': self.tmp_git_repo, 'commit_sha': commit_sha, 'commit_date': expected_date } self.assertEqualStdout( output, self.get_expected("test_config/test_config_from_file_debug_1", expected_kwargs))
def test_ignore_commits(self): """ Tests multiple commits of which some rules get igonored because of ignore-* rules """ # Create repo and some commits tmp_git_repo = self.create_tmp_git_repo() self.create_simple_commit(u"Sïmple title.\n\nSimple bödy describing the commit", git_repo=tmp_git_repo) # Normally, this commit will give T3 (trailing-punctuation), T5 (WIP) and B5 (bod-too-short) violations # But in this case only B5 because T3 and T5 are being ignored because of config self.create_simple_commit(u"Release: WIP tïtle.\n\nShort", git_repo=tmp_git_repo) # In the following 2 commits, the T3 violations are as normal self.create_simple_commit( u"Sïmple WIP title3.\n\nThis is \ta relëase commit\nMore info", git_repo=tmp_git_repo) self.create_simple_commit(u"Sïmple title4.\n\nSimple bödy describing the commit4", git_repo=tmp_git_repo) revlist = git("rev-list", "HEAD", _tty_in=True, _cwd=tmp_git_repo).split() config_path = self.get_sample_path("config/ignore-release-commits") output = gitlint("--commits", "HEAD", "--config", config_path, _cwd=tmp_git_repo, _tty_in=True, _ok_code=[4]) expected_kwargs = {"commit_sha0": revlist[0][:10], "commit_sha1": revlist[1][:10], "commit_sha2": revlist[2][:10], "commit_sha3": revlist[3][:10]} self.assertEqualStdout(output, self.get_expected("test_commits/test_ignore_commits_1", expected_kwargs))
def test_successful_merge_commit(self): # Create branch on master self.create_simple_commit(u"Cömmit on master\n\nSimple bödy") # Create test branch, add a commit and determine the commit hash git("checkout", "-b", "test-branch", _cwd=self.tmp_git_repo) git("checkout", "test-branch", _cwd=self.tmp_git_repo) commit_title = u"Commit on test-brånch with a pretty long title that will cause issues when merging" self.create_simple_commit(u"{0}\n\nSïmple body".format(commit_title)) hash = self.get_last_commit_hash() # Checkout master and merge the commit # We explicitly set the title of the merge commit to the title of the previous commit as this or similar # behavior is what many tools do that handle merges (like github, gerrit, etc). git("checkout", "master", _cwd=self.tmp_git_repo) git("merge", "--no-ff", "-m", u"Merge '{0}'".format(commit_title), hash, _cwd=self.tmp_git_repo) # Run gitlint and assert output is empty output = gitlint(_cwd=self.tmp_git_repo, _tty_in=True) self.assertEqualStdout(output, "") # Assert that we do see the error if we disable the ignore-merge-commits option output = gitlint("-c", "general.ignore-merge-commits=false", _cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[1]) self.assertEqual(output.exit_code, 1) self.assertEqualStdout( output, u"1: T1 Title exceeds max length (90>72): \"Merge '{0}'\"\n". format(commit_title))