From 392a200fd469c4161dbab5f2b59031a7a64f20a2 Mon Sep 17 00:00:00 2001 From: Jon Lockwood Date: Tue, 3 Aug 2021 13:26:05 +0930 Subject: [PATCH] feat(conventional_commits): Added conventional commit job Migrated conventional commits from: url: https://gitlab.com/nofusscomputing/infrastructure/ansible-roles commit: 037774e1e44f8e1e065718f805688b8b2f64735f updated logic so that it works on it's home repo and remote repo including private repositories. Changes to be committed: new file: conventional_commits/.gitlab-ci.yml new file: conventional_commits/README.md new file: conventional_commits/requirements.txt new file: conventional_commits/scripts/commit.py new file: conventional_commits/scripts/cz_junit.sh issue #1 --- conventional_commits/.gitlab-ci.yml | 73 +++++++++++++++++ conventional_commits/README.md | 94 +++++++++++++++++++++ conventional_commits/requirements.txt | 3 + conventional_commits/scripts/commit.py | 100 +++++++++++++++++++++++ conventional_commits/scripts/cz_junit.sh | 58 +++++++++++++ 5 files changed, 328 insertions(+) create mode 100644 conventional_commits/.gitlab-ci.yml create mode 100644 conventional_commits/README.md create mode 100644 conventional_commits/requirements.txt create mode 100755 conventional_commits/scripts/commit.py create mode 100755 conventional_commits/scripts/cz_junit.sh diff --git a/conventional_commits/.gitlab-ci.yml b/conventional_commits/.gitlab-ci.yml new file mode 100644 index 0000000..972bd97 --- /dev/null +++ b/conventional_commits/.gitlab-ci.yml @@ -0,0 +1,73 @@ + +.conventional_commit: + variables: + DEFAULT_ROOT_DIR: './gitlab-ci' + image: python:3.6-slim + stage: validation + before_script: + - mkdir -p "$CI_PROJECT_DIR/artifacts/$CI_JOB_STAGE/$CI_JOB_NAME" + - mkdir -p "$CI_PROJECT_DIR/artifacts/$CI_JOB_STAGE/tests" + - if [ "0$MR_ACCESS_TOKEN" == "0" ]; then MR_ACCESS_TOKEN=$CI_JOB_TOKEN; fi + - echo "[DEBUG] MR_ACCESS_TOKEN[$MR_ACCESS_TOKEN]" + - if [ "0$JOB_ROOT_DIR" == "0" ]; then ROOT_DIR=$DEFAULT_ROOT_DIR; else ROOT_DIR=$JOB_ROOT_DIR ; fi + - echo "[DEBUG] ROOT_DIR[$ROOT_DIR]" + - if [ "0$MY_PROJECT_ID" == "0" ]; then PROJECT_ID=$CI_PROJECT_ID; else PROJECT_ID=$MY_PROJECT_ID ; fi + - echo "[DEBUG] PROJECT_ID[$PROJECT_ID]" + - export PYTHON_VERSION=`python -c 'import sys; version=sys.version_info[:3]; print("{0}.{1}.{2}".format(*version))'` + - apt update + - apt install --no-install-recommends -y git + - python3 -m venv env + - . env/bin/activate + - pip install --upgrade pip + - pip install -r $ROOT_DIR/conventional_commits/requirements.txt + - echo "[DEBUG] CI_PROJECT_ID[$CI_PROJECT_ID]" + - echo "[DEBUG] CI_COMMIT_BRANCH[$CI_COMMIT_BRANCH]" + - $ROOT_DIR/conventional_commits/scripts/commit.py --token "$MR_ACCESS_TOKEN" --project $PROJECT_ID --branch $CI_COMMIT_BRANCH --target-branch + - target_branch=$($ROOT_DIR/conventional_commits/scripts/commit.py --token "$MR_ACCESS_TOKEN" --project $PROJECT_ID --branch $CI_COMMIT_BRANCH --target-branch) + + - echo "[DEBUG] Target Branch[$target_branch]" + - git clone --depth 150 -b $target_branch $CI_REPOSITORY_URL check + - cd check + - git remote rm origin + - git remote add origin $CI_REPOSITORY_URL + - git fetch --all + - git checkout --track origin/$CI_COMMIT_BRANCH + - git submodule update --init + - if [ -d "gitlab-ci" ]; then ls -la gitlab-ci; fi + - first_sha1=$(git log $target_branch..$CI_COMMIT_BRANCH --format=format:%H | tail -1) + - echo "[DEBUG] First Commit SHA[$first_sha1]" + - echo "[DEBUG] artifacts directory [$CI_PROJECT_DIR/artifacts/$CI_JOB_STAGE/$CI_JOB_NAME]" + after_script: + - ls -lR "$CI_PROJECT_DIR/artifacts/$CI_JOB_STAGE" + - cd .. + - rm -Rf check + artifacts: + expire_in: 3 days + when: always + paths: + - "$CI_PROJECT_DIR/artifacts/*" + reports: + junit: + - "$CI_PROJECT_DIR/artifacts/$CI_JOB_STAGE/tests/*.junit.xml" + rules: + - if: '$CI_COMMIT_BRANCH == "master"' + when: never + - if: '$CI_COMMIT_BRANCH' + when: always + - when: never + + +MR Title: + extends: + - .conventional_commit + script: + - cz_exit=0 && cz check --message "$($ROOT_DIR/conventional_commits/scripts/commit.py --token "$MR_ACCESS_TOKEN" --project $PROJECT_ID --title --branch $CI_COMMIT_BRANCH)" > "$CI_PROJECT_DIR/artifacts/$CI_JOB_STAGE/$CI_JOB_NAME/cz_output.log" 2>&1 || cz_exit=$? + - . $ROOT_DIR/conventional_commits/scripts/cz_junit.sh > "$CI_PROJECT_DIR/artifacts/$CI_JOB_STAGE/tests/$CI_JOB_NAME-cz.junit.xml" + + +Commit Messages: + extends: + - .conventional_commit + script: + - cz_exit=0 && cz check --rev-range $first_sha1..HEAD > "$CI_PROJECT_DIR/artifacts/$CI_JOB_STAGE/$CI_JOB_NAME/cz_output.log" 2>&1 || cz_exit=$? + - . $ROOT_DIR/conventional_commits/scripts/cz_junit.sh > "$CI_PROJECT_DIR/artifacts/$CI_JOB_STAGE/tests/$CI_JOB_NAME-cz.junit.xml" diff --git a/conventional_commits/README.md b/conventional_commits/README.md new file mode 100644 index 0000000..cfc2045 --- /dev/null +++ b/conventional_commits/README.md @@ -0,0 +1,94 @@ +# Conventional Commits User Manual +Commitizen is used to validate the format of commit messages. we use [Conventional Commit Messages](https://www.conventionalcommits.org/en/v1.0.0/) format for our validation jobs. + +This repository may have two CI jobs to do with commitizen: +- **MR Title** *Checks the Merge Request Title* +- **Commit Messages** *Checks all commit messages* + +These CI Jobs output a test report that can be viewed inside of the merge request and contain the error(s), if any. + +To fix an error please refer to the titled sections below. + +## MR Title +Ensure that the merge request title is in the [conventional message](https://www.conventionalcommits.org/en/v1.0.0/) format. NOTE: the title is case sensitive. + + +## Commit Messages +All commit messages that form part of your merge request must be in [conventional message](https://www.conventionalcommits.org/en/v1.0.0/) format. + +To fix them go back and edit your commit messages. + + +### fixing commit messages (suggestion) + +If only the last commit is the commit with an error just use `git commit --amend` and edit your commit message to be in the correct format and save. now push your changes. + + +You will require the following information if the commit message with the error is further down the commit tree: + - Commit message SHA1 of your first commit message to the branch `{original_commit}` + - Commit message SHA1 prior to your first commit `{source_commit}` + +Run these commands once you have the information above. +``` bash +git format-patch {original_commit}..HEAD -o diff-patches + +git reset {source_commit} --hard +``` + +Now, navigate to the `diff-patches` folder, open up the offending patch (commit) and edit the `subject` or message body as appropriate and save. Once all the edits have been done, re-apply the patches to your tree with: + +``` bash +git am diff-patches/*.patch +``` +Now push your changes upstream. + +| :notebook_with_decorative_cover: Note | +|:-----:| +| *As you have changed the commit SHA1(s), when you next push your changes upstream, you must force push. `git push --force`* | + +| :octagonal_sign: **WARNING** | +|:-----:| +| *Ensure that all of your commits were exported prior to reseting the branch and when re-applying, that all of your commits were applied correctly* | + + +# Conventional Commits Admin Manual Manual +This job checks commit messages on a branch and the merge request title for validity against the [conventional commit format](https://www.conventionalcommits.org/en/v1.0.0/) + +This job provides the following badge: + +- None + +## Dependencies + +- None + +## your .gitlab-ci.yml changes +To use this job add the following to your `.gitlab-ci.yml` file + +``` yaml +variables: + GIT_SUBMODULE_STRATEGY: recursive + MY_PROJECT_ID: "{yourproject id number}" + +stages: + - validation + +include: + - remote: https://gitlab.com/nofusscomputing/projects/gitlab-ci/-/raw/development/conventional_commits/.gitlab-ci.yml +``` + +## CI/CD Variables required + +| var name | Description | +|:----:|:----| +| MR_ACCESS_TOKEN | *only required if you are accessing a private repository.*
This token is a user access token that as a minimum requires read-only access to the api to fetch the projects merg requests. | + + +## Job Workflow + + +## Artifacts + + +## License +To view the license for this folder and any sub-folders, refer [here](https://gitlab.com/nofusscomputing/projects/gitlab-ci) diff --git a/conventional_commits/requirements.txt b/conventional_commits/requirements.txt new file mode 100644 index 0000000..5f8ca6f --- /dev/null +++ b/conventional_commits/requirements.txt @@ -0,0 +1,3 @@ +python-gitlab +requests +commitizen diff --git a/conventional_commits/scripts/commit.py b/conventional_commits/scripts/commit.py new file mode 100755 index 0000000..1379a5f --- /dev/null +++ b/conventional_commits/scripts/commit.py @@ -0,0 +1,100 @@ +#!/usr/bin/env python3 +#-*- coding: utf-8 -*- + + +#import gitlab +import os +import sys +import getopt +import json +import requests + +get_first_commit = False +get_mr_title = False +get_target_branch = False +project_id = '' + +try: + opts, args = getopt.getopt(sys.argv[1:],"hic:t:ti:p:b:o",["commit","token=", "title", "project=", "branch=", "target-branch"]) + +except getopt.GetoptError: + print('test.py [-c | --commit] [-t | --token {token}]') + sys.exit(2) + +for opt, arg in opts: + + #print('[DEBUG] {0} {1}'.format(opt, arg)) + if opt == '-h': + print('[commit.py] -i -o ') + sys.exit() + elif opt in ("-c", "--commit"): + get_first_commit = True + elif opt in ("-t", "--token"): + ci_job_token = arg + elif opt in ("-ti", "--title"): + get_mr_title = True + elif opt in ("-p", "--project"): + project_id = str(arg) + elif opt in ("-b", "--branch"): + git_branch = arg + elif opt in ("-o", "--target-branch"): + get_target_branch = True + +# private token or personal token authentication +#gl = gitlab.Gitlab('https://gitlab.com', private_token=ci_job_token) + + +url = 'https://gitlab.com/api/v4/projects/' + project_id + '/merge_requests' +headers = {'PRIVATE-TOKEN': ci_job_token} + +try: + if os.environ['CI_JOB_TOKEN'] == ci_job_token: + + headers = {'JOB_TOKEN': os.environ['CI_JOB_TOKEN']} + +except: + pass + +#print('[DEBUG] headers[{0}]'.format(headers)) + +merge_requests = requests.get(url, headers=headers, data='') + +merge_requests = merge_requests.json() + + +#print('\n\nmerge_requests=[-{0}-][]\n\n\n\n\n'.format(merge_requests)) + + +#project_mrs = project.mergerequests.list() +#mrs = gl.mergerequests.list() + + +mr_title = '' +mr_first_commit = '' +target_branch = '' + +for mr in merge_requests: + +# print('\n\nMR=[-{0}-]'.format(mr)) + + if mr['source_branch'] == git_branch and str(mr['target_project_id']) == str(project_id) and str(mr['state']) == 'opened': + mr_title = mr['title'] + mr_first_commit = mr['sha'] + target_branch = mr['target_branch'] + + + + +if get_target_branch: + print('{0}'.format(target_branch)) + + +if get_first_commit: + + print('{0}'.format(mr_first_commit)) + + +if get_mr_title: + + print('{0}'.format(mr_title)) + diff --git a/conventional_commits/scripts/cz_junit.sh b/conventional_commits/scripts/cz_junit.sh new file mode 100755 index 0000000..0465fe2 --- /dev/null +++ b/conventional_commits/scripts/cz_junit.sh @@ -0,0 +1,58 @@ +#!/bin/bash + +# to use ensure cz_exit=0 is set prior to the cz command so that if the command is successfull, this script works +# this script must be run with '. {script-name}' so that vars set in cli are available + +cz_command=$(cat "$CI_PROJECT_DIR/artifacts/$CI_JOB_STAGE/$CI_JOB_NAME/cz_output.log") + + +if [ "f${cz_exit}" == "f" ]; then +echo "[DEBUG] environmental variable cz_exit must be set" +exit 255 +fi + +if [ "${cz_exit}" == "0" ]; then +error_count=0 +system_err='' + +cat < + + +EOF + +else +error_count=1 +system_err="ERROR: $cz_command" + + +cat < + + + + $cz_command + + + + + + + + + + + +EOF + +fi + +#echo boo; + +#echo "output:[$cz_command]" +#echo "[DEBUG] cz_exit[$cz_exit]" + + + + +exit $cz_exit