Branch Structure & Git Workflow¶
This project follows a strict Gitflow-inspired workflow to ensure code quality and stability. Our CI/CD pipeline enforces formatting, linting, and commit styles.
Branching Strategy¶
We use a hierarchical branching model. Please adhere to the following naming conventions strictly.
gitGraph
commit id: "v0.9.0" tag: "v0.9.0"
branch develop
checkout develop
commit id: "init develop"
branch dev/tables
checkout dev/tables
commit id: "feat(api): tables client"
commit id: "feat(api): add caching"
checkout develop
merge dev/tables
branch dev/backend/database
checkout dev/backend/database
commit id: "feat(db): migrations"
checkout develop
merge dev/backend/database
branch fix/pipeline_fail
checkout fix/pipeline_fail
commit id: "fix(ci): lint config"
checkout develop
merge fix/pipeline_fail
branch dev/mkdocs
checkout dev/mkdocs
commit id: "docs: setup guide"
checkout develop
merge dev/mkdocs
checkout main
merge develop tag: "v1.0.0"
branch hotfix/v1.0.1
checkout hotfix/v1.0.1
commit id: "fix(critical): memory leak"
checkout main
merge hotfix/v1.0.1 tag: "v1.0.1"
checkout develop
merge hotfix/v1.0.1
Branch Types¶
We distinguish between protected integration branches and temporary working branches.
| Prefix | Usage | Example |
|---|---|---|
main |
Production Code. Only stable releases. Deploys to registry with Production Secrets. | main |
develop |
Integration Branch. All features merge here first. | develop |
dev/* |
Features. Implementation of new functionality or sub-projects. | dev/tablesdev/backend/database |
fix/* |
Bugfixes. Corrections for existing bugs. | fix/pipeline_failfix/login_error |
hotfix/* |
Critical Fixes. Urgent patches directly on main, generating a new patch release. |
hotfix/v1.0.1 |
Warning
Do not push directly to main or develop. Always use a Merge Request.
Git Workflow¶
1. Setup & Sync¶
Always start your work from the latest develop state to avoid conflicts.
See the Quick Setup Guide in the developer documentation for installation instructions.
2. Commit Guidelines (Strictly Enforced)¶
We follow Conventional Commits. The CI pipeline (commitlint) will reject your push if the format is incorrect.
Format: <type>(<scope>): <description>
| Type | Description | Example |
|---|---|---|
feat |
New feature | feat(ui): add new timetable view |
fix |
Bug fix | fix(db): resolve connection timeout |
docs |
Documentation only | docs(readme): update setup steps |
style |
Formatting | style(lint): fix pylint errors |
refactor |
Code change that fixes no bug/adds no feature | refactor(auth): simplify login logic |
test |
Adding missing tests | test(api): add unit test for login |
chore |
Build process, aux tools | chore(ci): update pipeline image |
3. Quality Checks (Local)¶
Before pushing, ensure your code passes our strict quality gates locally to save CI time.
-
pylint Must achieve a score of ≥ 9.5.
-
Type Checking: We use basedpyright (configured in
pyproject.toml).
Check pyproject.toml
[build-system]
requires = ["hatchling >= 1.27"]
build-backend = "hatchling.build"
[tool.hatch.build.targets.wheel]
packages = ["src/oscar", "src/util"]
# https://docs.basedpyright.com/latest/configuration/config-files/
[tool.basedpyright]
"typeCheckingMode" = "recommended"
# "reportUnusedFunction" = false
[tool.pylint]
# disable = "unused-function"
[tool.pylint.options]
good-names-rgx = "(_[a-z_]*|command|group|listen)"
[tool.pylint.messages_control]
disable = [
"C0114", # missing-module-docstring
"C0115", # missing-class-docstring
"C0116", # missing-function-docstring
"R0903", # too-few-public-methods
"W0511", # fixme (TODOs)
"R0801", # duplicate-code
]
[project.scripts]
oscar_ovgu = "oscar.main:main"
[project]
name = "oscar_ovgu"
version = "1.0.0"
authors = [
{ name="ISG", email="jana.goers@ovgu.de" },
]
description = "Organized Study Choice & Academic Roadmapper"
readme = "README.md"
requires-python = ">=3.12"
dependencies = [
"discord-py",
"python-dotenv",
"loguru",
"cyclopts",
"requests",
"rapidfuzz",
"translate",
"matplotlib>=3.9.4",
"numpy",
"toml"
]
classifiers = [
"Programming Language :: Python :: 3",
"Operating System :: OS Independent",
]
license = "Apache-2.0"
license-files = ["./license"]
[project.urls]
Homepage = "https://www.fin.ovgu.de/"
Issues = "https://www.fin.ovgu.de/Fakultaet.html"
[project.optional-dependencies]
dev = [
"pytest>=8.0.0",
"pytest-mock>=3.12.0",
"pytest-cov>=4.0.0",
"pylint>=3.0.0",
"pytest-asyncio>=0.23.0",
]
docs = [
"mkdocs-material>=9.6.20",
"mkdocstrings[python]>=0.30.1",
"mkdocs-glightbox",
"mkdocs-macros-plugin",
]
[tool.pytest.ini_options]
testpaths = ["tests"]
addopts = "-v --tb=short"
asyncio_mode = "auto"
filterwarnings = [
"ignore::DeprecationWarning:discord",
]
4. Push & Merge¶
Push your branch and open a Merge Request targeting develop.
CI/CD Pipeline & Deployment¶
The .gitlab-ci.yml pipeline handles verification and deployment automatically.
Check .gitlab-ci.yml
# based on: https://docs.gitlab.com/user/packages/package_registry/pypi_cosign_tutorial/#set-up-a-python-project
variables:
# Base Python version for all jobs
PYTHON_VERSION: "3.14-trixie"
# Package names and versions
PACKAGE_NAME: "oscar_ovgu"
PYLINT_FAIL_THRESHOLD: 9.5
# Pip and uv cache directory for faster builds
PIP_CACHE_DIR: "$CI_PROJECT_DIR/.pip-cache"
UV_CACHE_DIR: "$CI_PROJECT_DIR/.uv-cache"
# Template for Python-based jobs
.python-job:
image:
name: python:${PYTHON_VERSION}
pull_policy: if-not-present
before_script:
# Then install Python dependencies
- pip install uv
- uv pip install --system build twine setuptools wheel
- uv pip install --system .[dev,docs]
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- ${PIP_CACHE_DIR}
- ${UV_CACHE_DIR}
stages:
- lint
- test
- build
- publish
pylint:
extends: .python-job
stage: lint
script:
# - uv pip install --system .[dev]
# as of october 2025, the --jobs flag produces different scores for different values
- pylint --recursive=y --fail-under $PYLINT_FAIL_THRESHOLD src
- echo "Exit Code is $?"
after_script:
- >
if [ $CI_JOB_STATUS == "failed" ]; then
echo "Pylint Score too low for build, please improve style and commit again."
fi
commitlint:
stage: lint
image: registry.hub.docker.com/library/node:alpine
before_script:
- apk add --no-cache git
- npm install --save-dev @commitlint/config-conventional @commitlint/cli
script:
- |
if [ "$CI_COMMIT_BEFORE_SHA" = "0000000000000000000000000000000000000000" ]; then
npx commitlint --from HEAD~1 --to HEAD --verbose
else
npx commitlint --from $CI_COMMIT_BEFORE_SHA --to HEAD --verbose
fi
after_script:
- >
if [ "$CI_JOB_STATUS" = "failed" ]; then
echo "Commit messages must follow Conventional Commits, see docs/developer/workflow.md.";
echo "Keep the header at 120 characters or less. Long body lines only warn.";
fi
pytest:
extends: .python-job
stage: test
script:
- pytest --cov=src --cov-report=term-missing --cov-report=html:coverage_html
coverage: '/TOTAL.*\s+(\d+%)/'
artifacts:
paths:
- coverage_html/
expire_in: 30 days
docs-test:
extends: .python-job
stage: test
rules:
- if: $CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH
script:
- mkdocs build --strict
build:
extends: .python-job
stage: build
script:
# Initialize git repo with actual content
# - echo $CI_COMMIT_DESCRIPTION
# Debug: show updated file
- echo "Updated pyproject.toml contents:"
- cat pyproject.toml
# Build package
- python -m build
artifacts:
paths:
- dist/
- pyproject.toml
container_build_and_publish:
image:
name: quay.io/buildah/stable:latest
pull_policy: if-not-present
stage: publish
needs:
- build
variables:
STORAGE_DRIVER: vfs
IMAGE: 192.168.1.15:5050/studium-lehre/discord-bot
# The server starts the image without its own env, so the secrets are baked in (as before 3ce4726).
# Without them the bot crashes on login and podman auto-update silently rolls back.
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
before_script:
- ls dist
- echo "ref protected - $CI_COMMIT_REF_PROTECTED"
- |
missing=""
for var in BOT_TOKEN DISCORD_SERVER_ID TABLES_URL TABLES_USERNAME TABLES_PASSWORD; do
eval "value=\${$var}"
if [ -z "$value" ]; then
missing="$missing $var"
else
echo "$var is set (${#value} chars)"
fi
done
if [ -n "$missing" ]; then
echo "not available in this pipeline:$missing - refusing to publish an image that can't log in"
exit 1
fi
script:
- echo "$CI_REGISTRY_PASSWORD" | buildah login 192.168.1.15:5050 -u $CI_REGISTRY_USER --password-stdin --tls-verify=false
- >
buildah build --tls-verify=false
--build-arg "OSCAR_REVISION=$CI_COMMIT_SHORT_SHA"
--build-arg "BOT_TOKEN=$BOT_TOKEN"
--build-arg "DISCORD_SERVER_ID=$DISCORD_SERVER_ID"
--build-arg "TABLES_URL=$TABLES_URL"
--build-arg "TABLES_USERNAME=$TABLES_USERNAME"
--build-arg "TABLES_PASSWORD=$TABLES_PASSWORD"
-t "$IMAGE:latest" -t "$IMAGE:$CI_COMMIT_SHORT_SHA" .
# the server auto-pulls :latest, the sha tag makes rollbacks possible
- buildah push --tls-verify=false $IMAGE:latest
- buildah push --tls-verify=false $IMAGE:$CI_COMMIT_SHORT_SHA
pages:
extends: .python-job
stage: publish
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
script:
- ls
# - uv pip install --system .[docs]
- mkdocs build --site-dir public
artifacts:
paths:
- public
Pipeline Stages¶
- Lint (
lint):- Runs
pylint(Fails if score < 9.5). - Runs
commitlint(Checks commit message format).
- Runs
- Build (
build):- Builds the Python package (Wheel/Source) and updates
pyproject.tomlmetadata.
- Builds the Python package (Wheel/Source) and updates
- Publish (
publish):- Container Image: Builds the OCI image using
buildahand pushes to the internal OVGU registry (isggit3.cs.ovgu.de:5050/studium-lehre/discord-bot). - Documentation: Builds MkDocs and deploys to GitLab Pages.
- Container Image: Builds the OCI image using
Secret Management & Environments¶
The build process automatically injects the correct configuration based on the branch:
mainBranch:- Uses Production Tokens (
$BOT_TOKEN). - Deploys to the production namespace (
studium-lehre/discord-bot). - Other Branches (e.g.,
dev/*): - Uses Development Tokens (
$DEV_BOT_TOKEN). - Safe for testing without affecting the live bot.
Note: The
Containerfilerelies on these secrets being passed via CI/CD environment variables securely during the build and runtime. Do not commit secrets to the repository.
Automated Releases & Versioning¶
OSCAR uses automated Semantic Versioning based on Conventional Commits since the last release tag:
featbumps the MINOR version (e.g.,v0.2.0->v0.3.0).fixorperfbumps the PATCH version (e.g.,v0.2.0->v0.2.1).BREAKING CHANGEor!bumps the MAJOR version (e.g.,v0.2.0->v1.0.0).- All other commit types (
docs,style,chore, etc.) do not trigger a release bump.
Release Tool (tools/release_version.py)¶
The repository includes a dedicated release tool that inspects Git history:
- Dry run: Inspects commits since the latest reachable tag and reports the determined bump and next tag without changing files.
- Applying the bump:
Updates
pyproject.tomland writes environment variables (RELEASE_VERSION,RELEASE_TAG,RELEASE_BUMP) for downstream CI jobs.
Single Source of Truth¶
pyproject.toml is the sole source of truth for the project version. The documentation site dynamically resolves extra.version during build time via docs/main.py using get_project_metadata()["version"], preventing version drift across documentation and code.
CI Permissions & Tagging Setup¶
Automated tag pushes by GitLab CI require the job token to have push permissions:
- In GitLab under Settings -> CI/CD -> Token Access, ensure the project allows write access or configure a Project Access Token with
write_repositorypermission stored in CI variableGITLAB_TOKEN. - The release automation script runs on the default branch (
main) and refuses to tag on feature branches or when the tag already exists.
Verification & Functional Validation¶
To ensure the project goals (Efficient Planning, Module Discovery, Data Privacy) are met, every feature must be validated against the requirements.
Automated Tests¶
(Automated tests were planned during Phase 4 but ultimately de-scoped for v1.0.0 due to time constraints. The project relies on rigorous manual UI/UX testing). We aim to eventually add high test coverage in tests/ to validate:
- Search Logic: Does
/modulefind the correct entries? - Filter Logic: Does
/filterreturn valid subsets? - Persistence: Is the semester plan saved correctly?
Manual Verification (User Acceptance Testing)¶
Before merging to main, verify the following on the Test Server:
- Search: Query known modules (e.g., "Software Project") and check if details match the catalog.
- Planning: Add/Remove modules and restart the bot to ensure data persistence (Privacy/Database check).
- Usability: Ensure interaction flow (buttons/modals) is intuitive.
Troubleshooting & Common Issues¶
Pre-commit Hooks¶
If commitlint fails, check your commit message format.
Linting Errors¶
If pylint scores are low:
- Read the error report (it lists file and line number).
- Fix the issue (e.g., add missing docstring, rename variable).
- Run
pylint src/changed_file.pyto verify the fix.
Local Documentation¶
To preview documentation changes locally:
