Skip to content

Commit

Permalink
Merge pull request #11 from aj3sh/github-actions
Browse files Browse the repository at this point in the history
feat: added support for github actions
  • Loading branch information
aj3sh authored Jan 28, 2024
2 parents 9fa647c + 34b717a commit cd453e0
Show file tree
Hide file tree
Showing 5 changed files with 307 additions and 14 deletions.
18 changes: 18 additions & 0 deletions .github/workflows/test_action.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
name: Commitlint

on:
push:
branches: ["main"]
pull_request:

jobs:
commitlint:
runs-on: ubuntu-latest
name: Check commit messages
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Run commitlint
uses: ./ # Uses an action in the root directory
# or use a released GitHub Action
# uses: opensource-nepal/[email protected]
65 changes: 51 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,30 +1,67 @@
# commitlint

commitlint is is a pre-commit hook designed to lint your commit messages according to the [Conventional Commits](https://www.conventionalcommits.org/) standard.
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.

## How to use

### For pre-commit

1. Add the following configuration on `.pre-commit-config.yaml`.

```yaml
repos:
...
```yaml
repos:
...

- repo: https://github.com/opensource-nepal/commitlint
rev: 0.1.0
hooks:
- id: commitlint
- repo: https://github.com/opensource-nepal/commitlint
rev: 0.1.0
hooks:
- id: commitlint

...
```
...
```

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

```bash
pre-commit install --hook-type commit-msg
```
```bash
pre-commit install --hook-type commit-msg
```

> **_NOTE:_** Installing using only `pre-commit install` will not work.

### For github-actions

If you have any existing workflows, add the following steps:

```yaml
steps:
...
- name: Run commitlint
uses: opensource-nepal/[email protected]
...
```

If you don't have any workflows, create a new GitHub workflow, e.g. `.github/workflows/commitlint.yaml`.

```yaml
name: Commitlint
on:
push:
branches: ['main']
pull_request:
jobs:
commitlint:
runs-on: ubuntu-latest
name: Check commit messages
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Run commitlint
uses: opensource-nepal/[email protected]
```

> **_NOTE:_** Installing just using `pre-commit install` will not work.
> **_NOTE:_** commitlint GitHub Actions will only be triggered by "push" or "pull_request" events.

## Contribution

Expand Down
43 changes: 43 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
name: "commitlint"
description: "A GitHub Action to check conventional commit message"
runs:
using: "composite"
steps:
- name: Install Python
uses: actions/setup-python@v4
with:
python-version: "3.8"

- name: Install Commitlint
run: python -m pip install -e ${{ github.action_path }}
shell: bash

# checkout to the source code
# for push event
- name: Get pushed commit count
if: github.event_name == 'push'
id: push_commit_count
run: |
echo "count=$(echo '${{ toJson(github.event.commits) }}' | jq '. | length')" \
>> $GITHUB_OUTPUT
shell: bash
- name: Checkout to pushed commits
if: github.event_name == 'push'
uses: actions/checkout@v4
with:
ref: ${{ github.sha }}
fetch-depth: ${{ steps.push_commit_count.outputs.count }}

# for pull_request event
- name: Checkout to PR source branch
if: github.event_name == 'pull_request'
uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }}
fetch-depth: ${{ github.event.pull_request.commits }}

# checking the commits (for both push and pull_request)
- name: Check the commits
id: commitlint
run: python ${{ github.action_path }}/github_actions/run.py
shell: bash
98 changes: 98 additions & 0 deletions github_actions/event.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
"""
This module defines the `GithubEvent` class for handling GitHub event details.
Note:
This module relies on the presence of specific environment variables
set by GitHub Actions.
"""
import json
import os
from typing import Any, Dict


# pylint: disable=R0902; Too many instance attributes
class GithubEvent:
"""Class representing GitHub events.
This class provides methods for loading and accessing various details of
GitHub events.
Attributes:
event_name (str): The name of the GitHub event.
sha (str): The commit SHA associated with the event.
ref (str): The Git reference (branch or tag) for the event.
workflow (str): The name of the GitHub workflow.
action (str): The action that triggered the event.
actor (str): The GitHub username of the user or app that triggered the event.
job (str): The name of the job associated with the event.
run_attempt (str): The current attempt number for the job run.
run_number (str): The unique number assigned to the run by GitHub.
run_id (str): The unique identifier for the run.
event_path (str): The path to the file containing the GitHub event payload.
payload (dict): The GitHub event payload.
Raises:
EnvironmentError: If the required environment variable 'GITHUB_EVENT_PATH'
is not found.
Example:
```python
github_event = GithubEvent()
print(github_event.event_name)
print(github_event.sha)
print(github_event.payload)
```
"""

def __init__(self) -> None:
"""Initialize a new instance of the GithubEvent class."""
self.__load_details()

def __load_details(self) -> None:
"""
Load GitHub event details from environment variables and event payload file.
This method initializes the instance attributes by reading values from
environment variables set by GitHub Actions and loading the event payload
from a file.
"""
self.event_name = os.environ.get("GITHUB_EVENT_NAME")
self.sha = os.environ.get("GITHUB_SHA")
self.ref = os.environ.get("GITHUB_REF")
self.workflow = os.environ.get("GITHUB_WORKFLOW")
self.action = os.environ.get("GITHUB_ACTION")
self.actor = os.environ.get("GITHUB_ACTOR")
self.job = os.environ.get("GITHUB_JOB")
self.run_attempt = os.environ.get("GITHUB_RUN_ATTEMPT")
self.run_number = os.environ.get("GITHUB_RUN_NUMBER")
self.run_id = os.environ.get("GITHUB_RUN_ID")

if "GITHUB_EVENT_PATH" not in os.environ:
raise EnvironmentError("GITHUB_EVENT_PATH not found on the environment.")

self.event_path = os.environ["GITHUB_EVENT_PATH"]
with open(self.event_path, encoding="utf-8") as file:
self.payload = json.load(file)

def to_dict(self) -> Dict[str, Any]:
"""
Convert the GithubEvent instance to a dictionary.
Returns:
dict: A dictionary containing the attributes of the GithubEvent instance.
"""
return {
attr: getattr(self, attr)
for attr in dir(self)
if not callable(getattr(self, attr)) and not attr.startswith("__")
}

def __str__(self) -> str:
"""
Returns string representation of the github event data.
Returns:
str: Github event data.
"""
return str(self.to_dict())
97 changes: 97 additions & 0 deletions github_actions/run.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
"""
This script contains actions to be taken based on GitHub events,
specifically for push and pull_request events.
"""
import subprocess
import sys

from event import GithubEvent

EVENT_PUSH = "push"
EVENT_PULL_REQUEST = "pull_request"


def _handle_pr_event(event: GithubEvent) -> None:
"""
Handle pull_request GitHub event.
Args:
event (GithubEvent): An instance of the GithubEvent class representing
the GitHub event.
Raises:
EnvironmentError: If the base SHA and head SHA cannot be retrieved from
the event payload.
"""
try:
to_commit = event.payload["pull_request"]["head"]["sha"]

# getting from_commit using the total commits count
_total_commits = event.payload["pull_request"]["commits"]
from_commit = f"{to_commit}~{_total_commits-1}"
_check_commits(from_commit, to_commit)
except KeyError:
raise EnvironmentError("Unable to retrieve Base SHA and Head SHA") from None


def _handle_push_event(event: GithubEvent) -> None:
"""
Handle push GitHub event.
Args:
event (GithubEvent): An instance of the GithubEvent class representing
the GitHub event.
Raises:
EnvironmentError: If the from hash and to hash cannot be retrieved from
the event payload.
"""
try:
commits = event.payload["commits"]
from_commit = commits[0]["id"]
to_commit = commits[-1]["id"]
_check_commits(from_commit, to_commit)
except KeyError:
raise EnvironmentError("Unable to retrieve From hash and To hash") from None


def _check_commits(from_hash: str, to_hash: str) -> None:
"""Check commits using commitlint.
Args:
from_hash (str): The hash of the starting commit.
to_hash (str): The hash of the ending commit.
"""
sys.stdout.write(f"Commit from {from_hash} to {to_hash}\n")
try:
output = subprocess.check_output(
[
"commitlint",
"--from-hash",
from_hash,
"--to-hash",
to_hash,
],
text=True,
).strip()
sys.stdout.write(f"{output}\n")
except subprocess.CalledProcessError:
sys.exit(1)


def main() -> None:
"""Main entry point for the GitHub Actions workflow."""
event = GithubEvent()

if event.event_name == EVENT_PUSH:
_handle_push_event(event)
elif event.event_name == EVENT_PULL_REQUEST:
_handle_pr_event(event)
elif event.event_name is None:
sys.stdout.write("No any events, skipping\n")
else:
sys.stdout.write(f"Skipping for event {event.event_name}\n")


if __name__ == "__main__":
main()

0 comments on commit cd453e0

Please sign in to comment.