Skip to content

Commit cd453e0

Browse files
authored
Merge pull request #11 from aj3sh/github-actions
feat: added support for github actions
2 parents 9fa647c + 34b717a commit cd453e0

File tree

5 files changed

+307
-14
lines changed

5 files changed

+307
-14
lines changed

.github/workflows/test_action.yaml

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
name: Commitlint
2+
3+
on:
4+
push:
5+
branches: ["main"]
6+
pull_request:
7+
8+
jobs:
9+
commitlint:
10+
runs-on: ubuntu-latest
11+
name: Check commit messages
12+
steps:
13+
- name: Checkout
14+
uses: actions/checkout@v4
15+
- name: Run commitlint
16+
uses: ./ # Uses an action in the root directory
17+
# or use a released GitHub Action
18+
# uses: opensource-nepal/[email protected]

README.md

+51-14
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,67 @@
11
# commitlint
22

3-
commitlint is is a pre-commit hook designed to lint your commit messages according to the [Conventional Commits](https://www.conventionalcommits.org/) standard.
3+
commitlint is a tool designed to lint your commit messages according to the [Conventional Commits](https://www.conventionalcommits.org/) standard for your pre-commit hook and GitHub Actions.
44

55
## How to use
66

7+
### For pre-commit
8+
79
1. Add the following configuration on `.pre-commit-config.yaml`.
810

9-
```yaml
10-
repos:
11-
...
11+
```yaml
12+
repos:
13+
...
1214

13-
- repo: https://github.com/opensource-nepal/commitlint
14-
rev: 0.1.0
15-
hooks:
16-
- id: commitlint
15+
- repo: https://github.com/opensource-nepal/commitlint
16+
rev: 0.1.0
17+
hooks:
18+
- id: commitlint
1719

18-
...
19-
```
20+
...
21+
```
2022

2123
2. Install the `commit-msg` hook in your project repo:
2224

23-
```bash
24-
pre-commit install --hook-type commit-msg
25-
```
25+
```bash
26+
pre-commit install --hook-type commit-msg
27+
```
28+
29+
> **_NOTE:_** Installing using only `pre-commit install` will not work.
30+
31+
### For github-actions
32+
33+
If you have any existing workflows, add the following steps:
34+
35+
```yaml
36+
steps:
37+
...
38+
- name: Run commitlint
39+
uses: opensource-nepal/[email protected]
40+
...
41+
```
42+
43+
If you don't have any workflows, create a new GitHub workflow, e.g. `.github/workflows/commitlint.yaml`.
44+
45+
```yaml
46+
name: Commitlint
47+
48+
on:
49+
push:
50+
branches: ['main']
51+
pull_request:
52+
53+
jobs:
54+
commitlint:
55+
runs-on: ubuntu-latest
56+
name: Check commit messages
57+
steps:
58+
- name: Checkout
59+
uses: actions/checkout@v4
60+
- name: Run commitlint
61+
uses: opensource-nepal/[email protected]
62+
```
2663
27-
> **_NOTE:_** Installing just using `pre-commit install` will not work.
64+
> **_NOTE:_** commitlint GitHub Actions will only be triggered by "push" or "pull_request" events.
2865
2966
## Contribution
3067

action.yml

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
name: "commitlint"
2+
description: "A GitHub Action to check conventional commit message"
3+
runs:
4+
using: "composite"
5+
steps:
6+
- name: Install Python
7+
uses: actions/setup-python@v4
8+
with:
9+
python-version: "3.8"
10+
11+
- name: Install Commitlint
12+
run: python -m pip install -e ${{ github.action_path }}
13+
shell: bash
14+
15+
# checkout to the source code
16+
# for push event
17+
- name: Get pushed commit count
18+
if: github.event_name == 'push'
19+
id: push_commit_count
20+
run: |
21+
echo "count=$(echo '${{ toJson(github.event.commits) }}' | jq '. | length')" \
22+
>> $GITHUB_OUTPUT
23+
shell: bash
24+
- name: Checkout to pushed commits
25+
if: github.event_name == 'push'
26+
uses: actions/checkout@v4
27+
with:
28+
ref: ${{ github.sha }}
29+
fetch-depth: ${{ steps.push_commit_count.outputs.count }}
30+
31+
# for pull_request event
32+
- name: Checkout to PR source branch
33+
if: github.event_name == 'pull_request'
34+
uses: actions/checkout@v4
35+
with:
36+
ref: ${{ github.event.pull_request.head.sha }}
37+
fetch-depth: ${{ github.event.pull_request.commits }}
38+
39+
# checking the commits (for both push and pull_request)
40+
- name: Check the commits
41+
id: commitlint
42+
run: python ${{ github.action_path }}/github_actions/run.py
43+
shell: bash

github_actions/event.py

+98
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
"""
2+
This module defines the `GithubEvent` class for handling GitHub event details.
3+
4+
Note:
5+
This module relies on the presence of specific environment variables
6+
set by GitHub Actions.
7+
"""
8+
import json
9+
import os
10+
from typing import Any, Dict
11+
12+
13+
# pylint: disable=R0902; Too many instance attributes
14+
class GithubEvent:
15+
"""Class representing GitHub events.
16+
17+
This class provides methods for loading and accessing various details of
18+
GitHub events.
19+
20+
Attributes:
21+
event_name (str): The name of the GitHub event.
22+
sha (str): The commit SHA associated with the event.
23+
ref (str): The Git reference (branch or tag) for the event.
24+
workflow (str): The name of the GitHub workflow.
25+
action (str): The action that triggered the event.
26+
actor (str): The GitHub username of the user or app that triggered the event.
27+
job (str): The name of the job associated with the event.
28+
run_attempt (str): The current attempt number for the job run.
29+
run_number (str): The unique number assigned to the run by GitHub.
30+
run_id (str): The unique identifier for the run.
31+
32+
event_path (str): The path to the file containing the GitHub event payload.
33+
payload (dict): The GitHub event payload.
34+
35+
Raises:
36+
EnvironmentError: If the required environment variable 'GITHUB_EVENT_PATH'
37+
is not found.
38+
39+
Example:
40+
```python
41+
github_event = GithubEvent()
42+
print(github_event.event_name)
43+
print(github_event.sha)
44+
print(github_event.payload)
45+
```
46+
"""
47+
48+
def __init__(self) -> None:
49+
"""Initialize a new instance of the GithubEvent class."""
50+
self.__load_details()
51+
52+
def __load_details(self) -> None:
53+
"""
54+
Load GitHub event details from environment variables and event payload file.
55+
56+
This method initializes the instance attributes by reading values from
57+
environment variables set by GitHub Actions and loading the event payload
58+
from a file.
59+
"""
60+
self.event_name = os.environ.get("GITHUB_EVENT_NAME")
61+
self.sha = os.environ.get("GITHUB_SHA")
62+
self.ref = os.environ.get("GITHUB_REF")
63+
self.workflow = os.environ.get("GITHUB_WORKFLOW")
64+
self.action = os.environ.get("GITHUB_ACTION")
65+
self.actor = os.environ.get("GITHUB_ACTOR")
66+
self.job = os.environ.get("GITHUB_JOB")
67+
self.run_attempt = os.environ.get("GITHUB_RUN_ATTEMPT")
68+
self.run_number = os.environ.get("GITHUB_RUN_NUMBER")
69+
self.run_id = os.environ.get("GITHUB_RUN_ID")
70+
71+
if "GITHUB_EVENT_PATH" not in os.environ:
72+
raise EnvironmentError("GITHUB_EVENT_PATH not found on the environment.")
73+
74+
self.event_path = os.environ["GITHUB_EVENT_PATH"]
75+
with open(self.event_path, encoding="utf-8") as file:
76+
self.payload = json.load(file)
77+
78+
def to_dict(self) -> Dict[str, Any]:
79+
"""
80+
Convert the GithubEvent instance to a dictionary.
81+
82+
Returns:
83+
dict: A dictionary containing the attributes of the GithubEvent instance.
84+
"""
85+
return {
86+
attr: getattr(self, attr)
87+
for attr in dir(self)
88+
if not callable(getattr(self, attr)) and not attr.startswith("__")
89+
}
90+
91+
def __str__(self) -> str:
92+
"""
93+
Returns string representation of the github event data.
94+
95+
Returns:
96+
str: Github event data.
97+
"""
98+
return str(self.to_dict())

github_actions/run.py

+97
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
"""
2+
This script contains actions to be taken based on GitHub events,
3+
specifically for push and pull_request events.
4+
"""
5+
import subprocess
6+
import sys
7+
8+
from event import GithubEvent
9+
10+
EVENT_PUSH = "push"
11+
EVENT_PULL_REQUEST = "pull_request"
12+
13+
14+
def _handle_pr_event(event: GithubEvent) -> None:
15+
"""
16+
Handle pull_request GitHub event.
17+
18+
Args:
19+
event (GithubEvent): An instance of the GithubEvent class representing
20+
the GitHub event.
21+
22+
Raises:
23+
EnvironmentError: If the base SHA and head SHA cannot be retrieved from
24+
the event payload.
25+
"""
26+
try:
27+
to_commit = event.payload["pull_request"]["head"]["sha"]
28+
29+
# getting from_commit using the total commits count
30+
_total_commits = event.payload["pull_request"]["commits"]
31+
from_commit = f"{to_commit}~{_total_commits-1}"
32+
_check_commits(from_commit, to_commit)
33+
except KeyError:
34+
raise EnvironmentError("Unable to retrieve Base SHA and Head SHA") from None
35+
36+
37+
def _handle_push_event(event: GithubEvent) -> None:
38+
"""
39+
Handle push GitHub event.
40+
41+
Args:
42+
event (GithubEvent): An instance of the GithubEvent class representing
43+
the GitHub event.
44+
45+
Raises:
46+
EnvironmentError: If the from hash and to hash cannot be retrieved from
47+
the event payload.
48+
"""
49+
try:
50+
commits = event.payload["commits"]
51+
from_commit = commits[0]["id"]
52+
to_commit = commits[-1]["id"]
53+
_check_commits(from_commit, to_commit)
54+
except KeyError:
55+
raise EnvironmentError("Unable to retrieve From hash and To hash") from None
56+
57+
58+
def _check_commits(from_hash: str, to_hash: str) -> None:
59+
"""Check commits using commitlint.
60+
61+
Args:
62+
from_hash (str): The hash of the starting commit.
63+
to_hash (str): The hash of the ending commit.
64+
"""
65+
sys.stdout.write(f"Commit from {from_hash} to {to_hash}\n")
66+
try:
67+
output = subprocess.check_output(
68+
[
69+
"commitlint",
70+
"--from-hash",
71+
from_hash,
72+
"--to-hash",
73+
to_hash,
74+
],
75+
text=True,
76+
).strip()
77+
sys.stdout.write(f"{output}\n")
78+
except subprocess.CalledProcessError:
79+
sys.exit(1)
80+
81+
82+
def main() -> None:
83+
"""Main entry point for the GitHub Actions workflow."""
84+
event = GithubEvent()
85+
86+
if event.event_name == EVENT_PUSH:
87+
_handle_push_event(event)
88+
elif event.event_name == EVENT_PULL_REQUEST:
89+
_handle_pr_event(event)
90+
elif event.event_name is None:
91+
sys.stdout.write("No any events, skipping\n")
92+
else:
93+
sys.stdout.write(f"Skipping for event {event.event_name}\n")
94+
95+
96+
if __name__ == "__main__":
97+
main()

0 commit comments

Comments
 (0)