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) <noreply@anthropic.com>
This commit is contained in:
103
README.md
Normal file
103
README.md
Normal file
@@ -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 <actions@git.lair.cafe>` | 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.
|
||||||
62
action.yml
Normal file
62
action.yml
Normal file
@@ -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 <email@example.com>".
|
||||||
|
required: false
|
||||||
|
default: 'Gitea Actions <actions@git.lair.cafe>'
|
||||||
|
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"
|
||||||
86
scripts/generate-rpm-changelog.sh
Executable file
86
scripts/generate-rpm-changelog.sh
Executable file
@@ -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 <spec-file> <version>
|
||||||
|
#
|
||||||
|
# Environment overrides:
|
||||||
|
# CHANGELOG_AUTHOR — "Name <email>", default "Gitea Actions <actions@git.lair.cafe>"
|
||||||
|
# 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 <actions@git.lair.cafe>}"
|
||||||
|
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"
|
||||||
Reference in New Issue
Block a user