Releases automatiques avec cocogitto
Cocogitto valide les conventional commits, bumpe le semver automatiquement et génère un CHANGELOG. Tout se déclenche sur git push vers master, sans PR intermédiaire.
Cocogitto vs release-please
Les deux outils s'appuient sur les Conventional Commits et automatisent le versioning. Ils ont des philosophies opposées.
| cocogitto | release-please | |
|---|---|---|
| Déclencheur | Push direct sur master | Merge d'une Release PR |
| Validation locale | cog check, hook pre-commit | Aucune |
| Bump | Immédiat au push | À la merge de la PR |
| CHANGELOG | Format cog (- - -) | Keep a Changelog |
| GitHub Release | gh release create à ajouter | Créée automatiquement |
| Config | cog.toml | release-please-config.json |
| Mainteneur | Communauté |
En pratique, les deux flows divergent dès le premier push :
Release-please accumule les commits dans une PR ; la release n'a lieu que si quelqu'un merge manuellement, c'est un gate humain intégré. Cocogitto bumpe au push : zéro friction, mais zéro gate, il faut de la discipline sur les branches.
Bumps à répétition
Pousser plusieurs commits fix: directement sur master = autant de patch bumps. Travailler sur une branche feature et merger en une fois produit un seul bump.
Installation
# macOS
brew install cocogitto
# Linux (binaire direct)
curl -L https://github.com/cocogitto/cocogitto/releases/latest/download/cocogitto-x86_64-unknown-linux-musl.tar.gz \
| tar xz -C ~/.local/bin
Configuration
ignore_merge_commits = true
tag_prefix = "v" # obligatoire si vos tags existants sont en v*
[commit_types]
# Types custom pour le changelog
refacto = { changelog_title = "Refactoring" }
# perf ne bumpe pas par défaut — voir « Logique de bump »
perf = { changelog_title = "Performance Improvements", bump_patch = true }
[changelog]
path = "CHANGELOG.md"
template = "remote"
remote = "github.com"
repository = "mon-repo"
owner = "mon-org"
authors = []
tag_prefix
Sans tag_prefix = "v", cocogitto cherche des tags 1.2.3 (sans v). Si vos tags existants sont v1.2.3 (format release-please, goreleaser…), ce champ est obligatoire.
CHANGELOG.md
Cocogitto requiert un séparateur - - - pour savoir où insérer chaque nouvelle section. Le fichier minimal :
À chaque bump, cog insère le nouveau bloc entre le header et le - - - précédent :
# Changelog
- - -
## [v1.3.0](https://github.com/...) - 2026-05-11
#### Features
- (**api**) add pagination endpoint - ([abc1234](...)) - John Doe
- - -
## [v1.2.0](https://github.com/...) - 2026-05-01
...
Migration depuis release-please
Le CHANGELOG généré par release-please n'a pas de - - -. Il faut en ajouter un manuellement avant le premier cog bump sinon l'erreur cannot find default separator bloque le CI.
Validation locale
# Valider tous les commits depuis le dernier tag
cog check
# Valider uniquement depuis le dernier tag (plus rapide)
cog check --from-latest-tag
# Voir ce que le prochain bump produirait
cog bump --auto --dry-run
# Bumper manuellement
cog bump --patch # 1.2.3 → 1.2.4
cog bump --minor # 1.2.3 → 1.3.0
cog bump --major # 1.2.3 → 2.0.0
Hook pre-commit
Cog peut valider le message de commit avant qu'il soit enregistré :
repos:
- repo: local
hooks:
- id: cog-verify
name: Conventional commit check
language: system
entry: cog verify
stages: [commit-msg]
args: ["--file", ".git/COMMIT_EDITMSG"]
GitHub Actions
.github/workflows/release.yml
name: Release
on:
push:
branches: [master]
permissions:
contents: write
packages: write
jobs:
release:
runs-on: ubuntu-latest
outputs:
bumped: ${{ steps.bump.outputs.bumped }}
tag_name: ${{ steps.bump.outputs.tag_name }}
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 0 # obligatoire : cog a besoin de tout l'historique
token: ${{ secrets.GITHUB_TOKEN }}
- uses: cocogitto/cocogitto-action@v4
with:
git-user: github-actions[bot]
git-user-email: github-actions[bot]@users.noreply.github.com
command: check
args: --from-latest-tag # ne valide que les commits depuis le dernier tag
- name: Bump version
id: bump
run: |
BEFORE=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
cog bump --auto || true
AFTER=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
if [ "$BEFORE" != "$AFTER" ]; then
git push
git push origin "$AFTER"
echo "bumped=true" >> "$GITHUB_OUTPUT"
echo "tag_name=$AFTER" >> "$GITHUB_OUTPUT"
else
echo "bumped=false" >> "$GITHUB_OUTPUT"
fi
- name: Create GitHub release
if: steps.bump.outputs.bumped == 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
TAG="${{ steps.bump.outputs.tag_name }}"
NOTES=$(awk "/^## .*${TAG#v}/{found=1; next} found && /^- - -/{exit} found{print}" CHANGELOG.md)
gh release create "$TAG" \
--title "$TAG" \
--notes "${NOTES:-Release $TAG}" \
--verify-tag
build-push:
needs: release
if: needs.release.outputs.bumped == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
with:
ref: ${{ needs.release.outputs.tag_name }}
- uses: docker/setup-buildx-action@v4
- uses: docker/login-action@v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: docker/metadata-action@v6
id: meta
with:
images: ghcr.io/${{ github.repository }}
tags: |
type=semver,pattern={{version}},value=${{ needs.release.outputs.tag_name }}
type=semver,pattern={{major}}.{{minor}},value=${{ needs.release.outputs.tag_name }}
type=raw,value=latest # non généré automatiquement hors push de tag natif
type=sha,prefix=sha-
- uses: docker/build-push-action@v7
with:
push: true
platforms: linux/amd64,linux/arm64
cache-from: type=gha
cache-to: type=gha,mode=max
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cocogitto-action : nom du dépôt et migration v3 → v4
L'action vit sous cocogitto/cocogitto-action. Les exemples qui traînent en ligne référencent souvent oknozor/cocogitto-action : c'est le même dépôt, transféré du compte personnel du mainteneur vers l'organisation, et ça ne fonctionne que par la redirection GitHub. Autant pointer le nom canonique.
La v4 est cassante et l'échec est silencieux au moment du bump :
| v3 | v4 |
|---|---|
check: true (défaut) | command: check — obligatoire |
check-latest-tag-only: true | args: --from-latest-tag |
release: true | command: bump + args |
Un input inconnu ne fait qu'un warning côté Actions, mais command manquant fait sortir le script en erreur : Error: No command specified. Le job release échoue à chaque push sur master.
v4 embarque aussi cog 6.4.0 au lieu de 6.3.0 — à garder en tête si un comportement de bump change.
tag latest non automatique
docker/metadata-action ne génère latest automatiquement que sur un push de tag GitHub (on: push: tags: ['v*']). Ici le trigger est un push de branche, donc il faut type=raw,value=latest explicitement.
Logique de bump
| Commit | Bump |
|---|---|
fix: | patch (1.2.3 → 1.2.4) |
feat: | minor (1.2.3 → 1.3.0) |
feat!: ou BREAKING CHANGE | major (1.2.3 → 2.0.0) |
perf:, refactor: | aucun bump (configurable, voir ci-dessous) |
chore:, docs:, ci: | aucun bump |
perf: ne déclenche aucune release
Seuls feat et fix bumpent par défaut. Une PR ne contenant que des commits perf: produit un job de release vert mais sans tag :
Le piège est que tout a l'air de fonctionner : release passe en success, et ce sont les jobs conditionnés par bumped == 'true' (build Docker, publication Helm) qui sont skipped. L'optimisation reste sur master et ne sortira qu'au prochain fix: sans rapport, qui l'embarquera au passage.
Pour qu'un type déclenche un bump, [commit_types] accepte bump_patch et bump_minor :
Reprendre changelog_title explicitement : un CommitConfig défini sans ce champ perd le titre de section par défaut du type.
Vérifier avant de pousser
cog bump --auto --dry-run répond No conventional commits... ou la version calculée. C'est la façon la plus rapide de confirmer qu'un changement de [commit_types] produit bien l'effet attendu. Attention, cog bump refuse de tourner sur un arbre de travail sale : il affiche un git status au lieu du résultat. Committer d'abord.
Quand choisir quoi
Cocogitto si :
- Projet solo ou petite équipe, merge direct sur main
- Tu veux valider les commits localement avant push
- Tu veux des releases immédiates sans étape manuelle
release-please si :
- Équipe avec review obligatoire avant release
- Tu veux un gate humain explicite sur chaque release
- Tu travailles déjà avec les PR GitHub comme unité de travail