diff --git a/.travis.yml b/.travis.yml index da1c0c78..d9ba3202 100644 --- a/.travis.yml +++ b/.travis.yml @@ -41,5 +41,4 @@ script: - coverage run --source=openwisp_utils runtests.py after_success: - - coveralls - - python commitCheck.py $TRAVIS_REPO_SLUG $TRAVIS_COMMIT + coveralls diff --git a/openwisp_utils/qa.py b/openwisp_utils/qa.py index ca089d7a..deb75319 100644 --- a/openwisp_utils/qa.py +++ b/openwisp_utils/qa.py @@ -7,6 +7,8 @@ import os import re +import requests + def _parse_args(): """ @@ -51,3 +53,128 @@ def check_migration_name(): raise Exception("Migration %s %s in directory %s must " "be renamed to something more descriptive." % (file_, ', '.join(migrations), args.migration_path)) + + +def _commit_check_args(): + """ + Parse and return CLI arguements + """ + parser = argparse.ArgumentParser(description="Ensures migration files " + "created have a descriptive name. If " + "default name pattern is found, " + "raise exception!") + parser.add_argument("--token", + help="Github token for check commit") + parser.add_argument("--commit", + help="Commit sha for checking commit messages ") + parser.add_argument("--repo", + help="Repository target /") + return parser.parse_args() + + +def check_commit_message(): + args = _commit_check_args() + + if not args.token: + if "GH_TOKEN" in os.environ: + github_token = os.environ["GH_TOKEN"] + else: + raise Exception("CLI arguement `token` is required " + "but not found") + else: + github_token = args.token + + if not args.commit: + raise Exception("CLI argument `commit` is required " + "but not found") + commit = args.commit + if not args.repo: + raise Exception("CLI argument `repo` is required" + "but not found") + repo = args.repo + # Api URL and commit url + api = "https://api.github.com" + api_main_url = "/repos/" + repo + "/commits/" + commit + + res = requests.get(api + api_main_url) + api_body = res.json() + + # Get commit username + api_username = api_body['author']['login'] + # Get message commit + api_message = api_body['commit']['message'] + # Explode commit message + api_message_explode = api_message.split("\n") + # Count Api Message when exploded + api_message_explode_count = len(api_message_explode) + + errors = [] + + # Check dot in end of the first line + if api_message_explode[0][len(api_message_explode[0].strip()) - 1].strip() == '.': + errors.append("Do not add a final dot at the end of the first line.") + + # Check prefix + prefix = re.match('\[(.*?)\]', api_message_explode[0]) + + if not prefix: + errors.append( + "Add prefix in beginning of the commit. " + "Example: [module/file/feature]" + ) + else: + # Check capital after prefix + commitMessagefirst = api_message_explode[0].replace(prefix.group(), '').strip() + if not commitMessagefirst[0].isupper(): + errors.append("No capital letter after prefix") + + # Check hastag + hastag = re.search('\#(\d+)', api_message_explode[0]) + if not hastag: + errors.append("No mention issues in the short description") + + # Check blank page before first line and second line + if api_message_explode_count > 1: + print(api_message_explode[1]) + if api_message_explode[1] != '': + errors.append("No blank line before first line and second line") + + # Check is error + if len(errors) == 0: + body = "All check done, no errors." + else: + body = "You have errors with commit message: \n" + for e in errors: + body += "- " + e + "\n" + + search = requests.get( + api + + '/search/issues?q=typetype:pr%20sha:' + + _mb_substr(commit, 0, 7) + + '%20is_unmerged%20author:' + + api_username + + '&&sort=updated' + ) + search_body = search.json() + + if len(search_body['items']) > 0: + search_data = search_body['items'][0]['number'] + response_url = ( + "/repos/" + str(repo) + + "/issues/" + str(search_data) + "/comments") + + headers = { + 'Authorization': 'token %s' % github_token + } + post_fields = { + 'body': body + } + requests.post( + api + + response_url, + json=post_fields, + headers=headers) + + +def _mb_substr(s, start, length=None): + return (s[start:(start + length)]) diff --git a/setup.py b/setup.py index 2f03f5af..c643191d 100644 --- a/setup.py +++ b/setup.py @@ -33,7 +33,8 @@ packages=find_packages(exclude=['tests', 'docs']), entry_points={ 'console_scripts': [ - 'checkmigrations = openwisp_utils.qa:check_migration_name' + 'checkmigrations = openwisp_utils.qa:check_migration_name', + 'commitcheck = openwisp_utils.qa:check_commit_message', ], }, include_package_data=True, diff --git a/tests/test_project/tests/test_qa.py b/tests/test_project/tests/test_qa.py index bc346777..09fe3d4e 100644 --- a/tests/test_project/tests/test_qa.py +++ b/tests/test_project/tests/test_qa.py @@ -6,13 +6,16 @@ # Mock is a standard library from python3.3-pre onwards # from unittest.mock import patch from mock import patch -from openwisp_utils.qa import check_migration_name +from openwisp_utils.qa import check_commit_message, check_migration_name MIGRATIONS_DIR = path.join(path.dirname(path.dirname(path.abspath(__file__))), 'migrations') class TestQa(TestCase): _test_migration_file = '%s/0002_auto_20181001_0421.py' % MIGRATIONS_DIR + _gh_token = os.environ["GH_TOKEN"] + _repo = os.environ["TRAVIS_REPO_SLUG"] + _commit = os.environ["TRAVIS_COMMIT"] def setUp(self): # Create a fake migration file with default name @@ -44,5 +47,35 @@ def test_qa_call_check_migration_name_failure(self): else: self.fail('SystemExit or Exception not raised') + def test_qa_call_check_commit_message_pass(self): + options = [ + 'commitcheck', '--token', self._gh_token, + '--commit', self._commit, + '--repo', self._repo + ] + with patch('argparse._sys.argv', options): + check_commit_message() + + def test_qa_call_check_commit_message_failure(self): + options = [ + [ + 'commitcheck', '--token', self._gh_token, + '--commit', self._commit, + ], + [ + 'commitcheck', '--token', self._gh_token, + '--commit', self._repo, + ], + ['commitcheck'] + ] + for option in options: + with patch('argparse._sys.argv', option): + try: + check_commit_message() + except (SystemExit, Exception): + pass + else: + self.fail('SystemExit or Exception not raised') + def tearDown(self): os.unlink(self._test_migration_file)