diff --git a/bears/vcs/git/GitIgnoreBear.py b/bears/vcs/git/GitIgnoreBear.py new file mode 100644 index 0000000000..1ba12a5a40 --- /dev/null +++ b/bears/vcs/git/GitIgnoreBear.py @@ -0,0 +1,50 @@ +import shutil + +from coalib.bears.GlobalBear import GlobalBear +from coalib.misc.Shell import run_shell_command + + +class GitIgnoreBear(GlobalBear): + AUTHORS = {'The coala developers'} + AUTHORS_EMAILS = {'coala-devel@googlegroups.com'} + LICENSE = 'AGPL-3.0' + CAN_DETECT = {'Formatting'} + LANGUAGES = {'Git'} + + @classmethod + def check_prerequisites(cls): + if shutil.which('git') is None: + return 'git is not installed.' + else: + return True + + @staticmethod + def get_ignored_files(): + """ + This function checks for the files that are being tracked + but are ignored in .gitignore file. + Visit https://github.com/coala/coala-bears/issues/2610 + for more details. + + :return: + A list of details of tracked files that are + ignored in .gitignore file. + """ + files, _ = run_shell_command('git ls-files') + files = files.strip().split('\n') + ignored = list(map( + lambda file: run_shell_command( + 'git check-ignore --no-index -v {}'.format(file))[0].strip(), + files + )) + return list(filter(lambda f: f != '', ignored)) + + def run(self): + for line in GitIgnoreBear.get_ignored_files(): + pattern, filename = line.split('\t') + ignore_filename, line_number, ignore_regex = pattern.split(':') + yield self.new_result( + message='File {} is being tracked which was ignored in line ' + 'number {} in file {}.'.format( + filename, line_number, ignore_filename), + file=filename) diff --git a/tests/vcs/git/GitIgnoreBearTest.py b/tests/vcs/git/GitIgnoreBearTest.py new file mode 100644 index 0000000000..573b4970c2 --- /dev/null +++ b/tests/vcs/git/GitIgnoreBearTest.py @@ -0,0 +1,119 @@ +import os +import platform +import shutil +import stat +import unittest +import unittest.mock +from queue import Queue +from tempfile import mkdtemp + +from bears.vcs.git.GitIgnoreBear import GitIgnoreBear +from coalib.testing.BearTestHelper import generate_skip_decorator +from coalib.misc.Shell import run_shell_command +from coalib.settings.Section import Section + + +@generate_skip_decorator(GitIgnoreBear) +class GitIgnoreBearTest(unittest.TestCase): + + @staticmethod + def run_git_command(*args, stdin=None): + run_shell_command(' '.join(('git',) + args), stdin) + + def run_uut(self, *args, **kwargs): + """ + Runs the unit-under-test (via `self.uut.run()`) and collects the + messages of the yielded results as a list. + + :param args: Positional arguments to forward to the run function. + :param kwargs: Keyword arguments to forward to the run function. + :return: A list of the message strings. + """ + return list(result.message for result in self.uut.run(*args, **kwargs)) + + def assert_no_msgs(self): + """ + Assert that there are no messages in the message queue of the bear, and + show the messages in the failure message if it is not empty. + """ + self.assertTrue( + self.msg_queue.empty(), + 'Expected no messages in bear message queue, but got: ' + + str(list(str(i) for i in self.msg_queue.queue))) + + def setUp(self): + self.msg_queue = Queue() + self.section = Section('') + self.uut = GitIgnoreBear(None, self.section, self.msg_queue) + + self._old_cwd = os.getcwd() + self.gitdir = mkdtemp() + os.chdir(self.gitdir) + self.run_git_command('init') + self.run_git_command('config', 'user.email coala@coala.io') + self.run_git_command('config', 'user.name coala') + + @staticmethod + def _windows_rmtree_remove_readonly(func, path, excinfo): + os.chmod(path, stat.S_IWRITE) + func(path) + + def tearDown(self): + os.chdir(self._old_cwd) + if platform.system() == 'Windows': + onerror = self._windows_rmtree_remove_readonly + else: + onerror = None + shutil.rmtree(self.gitdir, onerror=onerror) + + def test_check_prerequisites(self): + _shutil_which = shutil.which + try: + shutil.which = lambda *args, **kwargs: None + self.assertEqual(GitIgnoreBear.check_prerequisites(), + 'git is not installed.') + + shutil.which = lambda *args, **kwargs: 'path/to/git' + self.assertTrue(GitIgnoreBear.check_prerequisites()) + finally: + shutil.which = _shutil_which + + def test_no_tracked_files(self): + self.assertEqual(self.run_uut(), []) + self.assert_no_msgs() + + def test_no_gitignore_file(self): + file = open('test_file.txt', 'w') + file.close() + self.run_git_command('add', 'test_file.txt') + + self.assertEqual(self.run_uut(), []) + self.assert_no_msgs() + + def test_already_tracked_file(self): + file = open('test_file.txt', 'w') + file.close() + self.run_git_command('add', 'test_file.txt') + + file = open('.gitignore', 'w') + file.write('test_file.txt') + file.close() + + self.run_git_command('add', '.gitignore') + self.assertEqual(self.run_uut(), [ + 'File test_file.txt is being tracked which was ignored in line' + ' number 1 in file .gitignore.' + ]) + self.assert_no_msgs() + + def test_untracked_file(self): + file = open('test_file.txt', 'w') + file.close() + + file = open('.gitignore', 'w') + file.write('test_file.txt') + file.close() + + self.run_git_command('add', '.gitignore') + self.assertEqual(self.run_uut(), []) + self.assert_no_msgs()