Skip to content

Commit

Permalink
feat: prepared tool code
Browse files Browse the repository at this point in the history
  • Loading branch information
panpuchkov committed Dec 23, 2023
1 parent add3a3e commit ca7a83c
Show file tree
Hide file tree
Showing 26 changed files with 1,619 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.pytest_cache/*
venv
.tox
7 changes: 7 additions & 0 deletions .flake8rc
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[flake8]
ignore = E501
max-line-length = 120
exclude =
.git,
.tox,
venv
56 changes: 56 additions & 0 deletions .github/workflows/build.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
name: Docker Build Image & Push (CICDv2)
permissions: write-all
'on':
push:
branches:
- main
- feat/*
- feature/*
- fix/*
- bugfix/*
pull_request:
branches:
- main
env:
IMAGE_NAME: 'pygitver'
REGISTRY_NAME: pygitver

jobs:
unittests:
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Unit Tests
shell: bash
run: |
set -x
pip install -U tox
pip install tox
tox
build:
runs-on: ubuntu-latest
needs: unittests
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v3

- name: Docker Build
shell: bash
run: |
set -x
docker build -t ${{ env.IMAGE_NAME }} .
- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
username: panpuchkov
password: ${{ secrets.DOCKER_PASSWORD }}

- name: Docker Push
shell: bash
run: |
set -x
echo "TODO"
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.idea
.tox
.coverage
*/__pychache__/*
venv
32 changes: 32 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# syntax=docker/dockerfile:1
FROM python:3.12-alpine as build

ENV SEMVER_HELPER_TEMPLATE_CHANGELOG="/pygitver/templates/changelog.tmpl"
ENV SEMVER_HELPER_ROOT="/pygitver"

# Make sure we use the virtualenv:
RUN python -m venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"

# Install pip requirements
COPY ./requirements.txt /pygitver/requirements.txt
RUN pip install -U pip \
&& pip install -r /pygitver/requirements.txt --no-cache-dir

FROM python:3.12-alpine

# Install dependencies
RUN apk add --no-cache git openssh \
&& ln -s /pygitver/pygitver /usr/local/bin/pygitver

# Copy application code and
COPY ./src /pygitver
COPY ./scripts /pygitver/scripts
COPY --from=build /opt/venv /opt/venv

WORKDIR /app

# Make sure we use the virtualenv:
ENV PATH="/opt/venv/bin:$PATH"

ENTRYPOINT ["python", "/pygitver/pygitver.py"]
157 changes: 157 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
# Semver-Helper

Features:
* Conventional Commit linter
* Generate CHANGELOG
* Bump version based on CHANGELOG

## Conventional Commits Rules
Tool supports simplified Conventional Commits which are described in this section.

The commit message should be structured as follows:
```shell
<type>[optional scope]: <description>
```

The commit contains the following structural elements, to communicate intent to the consumers of your library:
* `fix:` a commit of the type fix patches a bug in your codebase (this correlates with `PATCH` in Semantic Versioning).
* `feat:` a commit of the type feat introduces a new feature to the codebase (this correlates with `MINOR` in Semantic Versioning).
* `BREAKING CHANGE:` a commit that has a footer `BREAKING CHANGE:`, or appends a `!` after the type/scope, introduces a breaking API change (correlating with `MAJOR` in Semantic Versioning). A BREAKING CHANGE can be part of commits of any type.
* Other allowed prefixes: `build:`, `chore:`, `ci:`, `docs:`, `style:`, `refactor:`, `perf:`, `test:`. These correlate with `PATCH` in Semantic Versioning.

### Conventional Commits Examples:

Commit without scope without breaking change
```
fix: crash on wrong input data
```


Commit message with ! to draw attention to breaking change
```
feat!: send an email to the customer when a product is shipped
```


Commit message with scope and ! to draw attention to breaking change
```
feat(api)!: send an email to the customer when a product is shipped
```


Commit message with scope
```
feat(lang): add Polish language
```


## Users (Developers) Section


### Install Semver-Helper
Run in the `git` root folder of the target repository on localhost.
```shell
docker run --rm -v $(pwd):/app -w /app --entrypoint '' panpuchkov/pygitver /pygitver/scripts/install.sh
```

* It doesn't matter what the current branch is.
* You should install it in every repository that needs conventional commit messages.

### Update Semver-Helper

Run in terminal in any folder:

```shell
docker pull panpuchkov/pygitver
```

### Usage

You don't need to use it directly, it will be used automatically on each git commit.

_Example of a commit message that is **NOT VALID** for Conventional Commits:_
```shell
$ git commit -am "test"
ERROR: Commit does not fit Conventional Commits requirements
```

_Example of a commit message that is **VALID** for Conventional Commits:_
```shell
$ git commit -am "feat: test"
[feature/test2 af1a5c4] feat: test
1 file changed, 1 deletion(-)
```

_Note: repeat this procedure for each repository_

## DevOps and Tool Developers Section

### Usage

_**Note:** The tool is running in the docker container and mounting your repository to it.
It means that you have to run it just from the root directory of the repository,
from the directory with `.git` folder._

```shell
docker run --rm -v $(pwd):/app -w /app panpuchkov/pygitver --help
```

#### Examples

Check if the git commit message is valid for Conventional Commits:
```shell
$ docker run --rm -v $(pwd):/app -w /app panpuchkov/pygitver --check-commit-message "feat: conventional commit message"
$ echo $? # get exit code
0
$ docker run --rm -v $(pwd):/app -w /app panpuchkov/pygitver --check-commit-message "non-conventional commit message"
ERROR: Commit does not fit Conventional Commits requirements
$ echo $? # get exit code
1
```

Get current version (last git tag):
```shell
$ docker run --rm -v $(pwd):/app -w /app panpuchkov/pygitver --curr-ver
v0.0.3
```

Get next version (bump last git tag):
```shell
$ docker run --rm -v $(pwd):/app -w /app panpuchkov/pygitver --next-ver
v1.0.0
```

#### Custom CHANGELOG Templates

* Take as an example: `./src/templates/changelog.tmpl`
* Place somewhere in your project custom template
* Send environment variable `SEMVER_HELPER_TEMPLATE_CHANGELOG` to docker
on run with full template path in Docker (usually `/app/...`)

### Development

#### Build Docker
```shell
docker build -t pygitver .
```

#### Install to Localhost
```shell
pip install -r requirements-dev.txt
```

#### Test on Localhost

##### Run all checks
```shell
tox
```

##### A single file of the test run
```shell
tox -e coverage -- ./tests/test_git.py -vv
```
or
```shell
coverage run -m pytest -- ./tests/test_git.py
```
2 changes: 2 additions & 0 deletions pytest.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[pytest]
pythonpath = ./src
7 changes: 7 additions & 0 deletions requirements-dev.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Jinja2

tox
pytest
black
mypy
docformatter
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Jinja2==3.1.2
16 changes: 16 additions & 0 deletions scripts/git/hooks/commit-msg
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#!/bin/sh

# pygitver - commit-msg git hook
COMMIT_MSG=$1
COMMIT_LINT_DOCKER="panpuchkov/pygitver"
docker run --rm -v $(pwd):/app -w /app ${COMMIT_LINT_DOCKER} --check-commit-message "$(cat \"${COMMIT_MSG}\")"

COMMIT_LINT_CHECK="$?"
if [[ 0 -ne "${COMMIT_LINT_CHECK}" ]]; then
exit ${COMMIT_LINT_CHECK};
fi

# update docker image in background (optional, uncomment next line if you want auto update)
# docker pull ${COMMIT_LINT_DOCKER} >> /dev/null &

# pygitver - commit-msg git hook ^^^
18 changes: 18 additions & 0 deletions scripts/install.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#!/bin/sh

SEMVER_ROOT="/pygitver"
GIT_HOOK_COMMIT_MSG_FILE_DST=".git/hooks/commit-msg"
GIT_HOOK_COMMIT_MSG_FILE_SRC="${SEMVER_ROOT}/scripts/git/hooks/commit-msg"

echo "Installing git hook.";
if [ ! -r ".git" ]; then
echo "Directory '.git' not found. Run in the root directory of the repository."
fi

if [ ! -r "${GIT_HOOK_COMMIT_MSG_FILE_DST}" ]; then
cp "${GIT_HOOK_COMMIT_MSG_FILE_SRC}" ${GIT_HOOK_COMMIT_MSG_FILE_DST}
chmod +x ${GIT_HOOK_COMMIT_MSG_FILE_DST}
echo "Done."
else
echo "Git hook commit-msg is already exists, please check if it is correct."
fi
Empty file added src/__init__.py
Empty file.
63 changes: 63 additions & 0 deletions src/changelogs_mngr.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import os
import json
from git import Git, GitError

from jinja2 import Environment, FileSystemLoader, TemplateNotFound


class ChangelogsMngrError(Exception):
pass


class ChangelogsMngr:
def __init__(self, changelogs_version: str = "0.0.1") -> None:
self._changelogs_version = changelogs_version
self._init_changelog()

def _init_changelog(self):
self._changelogs: dict = {"version": self._changelogs_version, "services": {}}
self._bump_version_rules: dict = {
"major": False,
"minor": False,
"patch": False,
}

def _update_bump_version_rules(self, service_name: str) -> None:
for key in self._bump_version_rules.keys():
self._bump_version_rules[key] |= self._changelogs["services"][service_name][
"bump_rules"
][key]

def read_files(self, path: str, file_ext: str = "json"):
self._init_changelog()
for file_name in sorted(os.listdir(path)):
try:
with open(os.path.join(path, file_name)) as fp:
if file_ext and file_name.endswith(f".{file_ext}"):
file_name = file_name[: -(len(file_ext) + 1)]
self._changelogs["services"][file_name] = json.load(fp)
self._update_bump_version_rules(file_name)
except json.JSONDecodeError:
# nothing to do, just skip invalid file
pass
try:
self._changelogs["version"] = Git.bump_version(
self._changelogs["version"],
self._bump_version_rules,
)
except GitError: # pragma: no cover
self._changelogs["version"] = None
return self._changelogs

def changelog_generate(
self, template_name: str = "templates/changelog-common.tmpl"
) -> str:
try:
env = Environment(loader=FileSystemLoader(os.path.dirname(template_name)))
template = env.get_template(os.path.basename(template_name))
output = template.render(**self._changelogs)
except TemplateNotFound:
raise ChangelogsMngrError(
f"ERROR: Template '{template_name}' was not found."
)
return output
Loading

0 comments on commit ca7a83c

Please sign in to comment.