commit 21de5b2b8726146125d2744e85027baf2fd370df Author: rob thijssen Date: Thu Apr 16 15:20:30 2026 +0300 feat: initial rpm-changelog composite action Generates an rpm %changelog entry from git history and prepends it to the target spec file. Runs in release CI so every tag push gets a fresh dated entry covering the commits since the previous matching tag. Avoids stale dates, forgotten bumps, and bogus-weekday warnings. Inputs: spec path, version, optional release suffix, author, tag-pattern, exclude-patterns. The last two make the defaults (v* tags, filter bump-version chores and merges) tweakable for projects with different conventions. Migrated from inline logic in helexa/cortex so any project can adopt the same pattern with a single uses: reference. Co-Authored-By: Claude Opus 4.6 (1M context) diff --git a/README.md b/README.md new file mode 100644 index 0000000..9389404 --- /dev/null +++ b/README.md @@ -0,0 +1,103 @@ +# rpm-changelog + +Gitea composite action that generates an rpm `%changelog` entry from +git history and prepends it to a spec file. Designed to run during +release CI so the changelog never drifts out of sync with the code. + +## Why use this + +Hand-maintained rpm `%changelog` sections drift. The day-of-week gets +stale, the version lags behind tags, and the description stops +matching what actually changed. This action: + +- Finds the previous release tag (default pattern `v*`). +- Collects commits between that tag and `HEAD` via `git log`. +- Filters out noise (bump-version bot commits, merge commits — both + configurable). +- Writes a fresh `%changelog` entry with today's date, the release + author, and the new version. +- Prepends it to the spec file's existing `%changelog` section. + +Because the date is generated at build time, `rpmbuild` will never +warn about bogus weekdays and the entry always reflects what changed +since the last release. + +## Requirements + +The consumer workflow must check out the repo with full git history: + +```yaml +- uses: actions/checkout@v4 + with: + fetch-depth: 0 +``` + +Without this, `git describe` can't see prior tags and the entry will +default to "No user-visible changes" on every release. + +## Inputs + +| Input | Required | Default | Description | +| ------------------ | -------- | --------------------------------------------- | --------------------------------------------------------------------------- | +| `spec` | yes | — | Path to the rpm spec file to update. | +| `version` | yes | — | Version string for the new entry (without release suffix), e.g. `0.1.10`. | +| `release` | no | `1` | Release suffix to append after the version. | +| `author` | no | `Gitea Actions ` | Name and email for the entry. | +| `tag-pattern` | no | `v*` | Glob pattern for release tags, used to locate the previous release. | +| `exclude-patterns` | no | (see below) | Newline-separated `grep -E` patterns to drop from the generated log. | + +Default `exclude-patterns`: + +``` +^- chore: bump version +^- Merge +``` + +## Usage + +```yaml +jobs: + srpm: + runs-on: fedora + if: startsWith(github.ref, 'refs/tags/v') + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Determine version + id: version + run: echo "VERSION=${GITHUB_REF#refs/tags/v}" >> "$GITHUB_OUTPUT" + + - name: Stamp version in spec + run: sed -i "s/^Version:.*/Version: ${{ steps.version.outputs.VERSION }}/" mypackage.spec + + - name: Update changelog + uses: https://git.lair.cafe/actions/rpm-changelog@v1 + with: + spec: mypackage.spec + version: ${{ steps.version.outputs.VERSION }} + + - name: Build SRPM + run: rpmbuild -bs mypackage.spec --define "_sourcedir $(pwd)" +``` + +Note the fully-qualified URL in `uses:` — the Gitea instance's +`DEFAULT_ACTIONS_URL` points at github.com, so internal actions must +be referenced by absolute URL. + +## Versioning + +Pin to a major version tag (`@v1`) for automatic patch/minor updates. +Pin to an exact tag (`@v1.0.2`) to freeze. + +## Behaviour notes + +- If no previous tag matching `tag-pattern` exists (first release), + the entry body becomes `- No user-visible changes`. +- If the filter removes every commit (e.g. release contained only + bump-version chores), body is `- No user-visible changes`. +- The spec file must already contain a `%changelog` line — the + action will error out rather than mangle an unmarked file. +- The date uses UTC (`date -u`) so runners in different timezones + produce deterministic output. diff --git a/action.yml b/action.yml new file mode 100644 index 0000000..7a42eae --- /dev/null +++ b/action.yml @@ -0,0 +1,62 @@ +name: 'Generate RPM %changelog entry' +description: > + Collect commits since the previous release tag and prepend a + properly-formatted %changelog entry to an rpm spec file. Keeps + the changelog in sync with git history automatically on every + release, avoiding stale dates and forgotten bumps. +author: 'helexa' + +branding: + icon: file-text + color: blue + +inputs: + spec: + description: 'Path to the rpm spec file to update.' + required: true + version: + description: > + Version string for the new entry (without release suffix). + Typically derived from the git tag, e.g. "0.1.10". + required: true + release: + description: 'Release suffix to append after the version.' + required: false + default: '1' + author: + description: > + Name and email for the changelog entry, formatted as + "Name ". + required: false + default: 'Gitea Actions ' + tag-pattern: + description: > + Glob pattern for release tags, used to locate the previous + release. Default matches the conventional "v*" scheme. + required: false + default: 'v*' + exclude-patterns: + description: > + Newline-separated list of grep -E patterns (anchored on the + bullet "- " prefix) to drop from the generated log. Defaults + filter out bump-version bot commits and merge commits. + required: false + default: | + ^- chore: bump version + ^- Merge + +runs: + using: composite + steps: + - name: Generate changelog entry + shell: bash + env: + SPEC: ${{ inputs.spec }} + VERSION: ${{ inputs.version }} + RELEASE: ${{ inputs.release }} + CHANGELOG_AUTHOR: ${{ inputs.author }} + TAG_PATTERN: ${{ inputs.tag-pattern }} + EXCLUDE_PATTERNS: ${{ inputs.exclude-patterns }} + run: | + bash "${{ github.action_path }}/scripts/generate-rpm-changelog.sh" \ + "$SPEC" "$VERSION" diff --git a/scripts/generate-rpm-changelog.sh b/scripts/generate-rpm-changelog.sh new file mode 100755 index 0000000..c19a7bb --- /dev/null +++ b/scripts/generate-rpm-changelog.sh @@ -0,0 +1,86 @@ +#!/bin/bash +# Generate an rpm %changelog entry for this release and prepend it to +# the spec's existing %changelog section. +# +# Usage: generate-rpm-changelog.sh +# +# Environment overrides: +# CHANGELOG_AUTHOR — "Name ", default "Gitea Actions " +# RELEASE — release suffix, default "1" +# TAG_PATTERN — glob for release tags, default "v*" +# EXCLUDE_PATTERNS — newline-separated grep -E patterns to drop +# +# Collects commits since the previous matching tag, drops filtered +# lines (bump-version chore commits, merges, etc.), and writes a +# dated entry. + +set -euo pipefail + +SPEC="$1" +VERSION="$2" +AUTHOR="${CHANGELOG_AUTHOR:-Gitea Actions }" +RELEASE="${RELEASE:-1}" +TAG_PATTERN="${TAG_PATTERN:-v*}" +EXCLUDE_PATTERNS="${EXCLUDE_PATTERNS:-$'^- chore: bump version\n^- Merge'}" + +if [ ! -f "$SPEC" ]; then + echo "error: spec file not found: $SPEC" >&2 + exit 1 +fi + +if ! grep -q '^%changelog' "$SPEC"; then + echo "error: no %changelog section in $SPEC — refusing to mangle" >&2 + exit 1 +fi + +# Find the previous release tag (exclude the current tag at HEAD). +PREV_TAG=$(git describe --tags --abbrev=0 --match="$TAG_PATTERN" HEAD^ 2>/dev/null || echo "") + +if [ -n "$PREV_TAG" ]; then + RAW=$(git log --no-merges --pretty=format:'- %s' "${PREV_TAG}..HEAD") + + # Build a combined grep -E pattern from the exclude list; drop blanks. + COMBINED="" + while IFS= read -r pat; do + [ -z "$pat" ] && continue + if [ -z "$COMBINED" ]; then + COMBINED="$pat" + else + COMBINED="${COMBINED}|${pat}" + fi + done <<< "$EXCLUDE_PATTERNS" + + if [ -n "$COMBINED" ]; then + LOG=$(printf '%s\n' "$RAW" | grep -Ev "$COMBINED" || true) + else + LOG="$RAW" + fi +else + LOG="" +fi + +if [ -z "$LOG" ]; then + LOG="- No user-visible changes" +fi + +DATE=$(LC_ALL=C date -u +'%a %b %d %Y') +ENTRY="* ${DATE} ${AUTHOR} - ${VERSION}-${RELEASE} +${LOG} +" + +# Prepend the entry to the %changelog section. +TMP=$(mktemp) +awk -v entry="$ENTRY" ' + inserted == 0 && /^%changelog[[:space:]]*$/ { + print + print entry + inserted = 1 + next + } + { print } +' "$SPEC" > "$TMP" + +mv "$TMP" "$SPEC" + +echo "Added changelog entry for ${VERSION}-${RELEASE}:" +echo "$ENTRY"