Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changes/home-of-nonroot-from-var-lib.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
type: changed
---
Home of `nonroot` from `/var/lib/sqlite` to `/home/nonroot`, so `.sqlite_history` and whatever else a program writes under `HOME` stay out of the data directory

- The working directory stays `/var/lib/sqlite`, where the databases are
6 changes: 6 additions & 0 deletions .changes/sbom-document-in-var-lib-db.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
type: fixed
---
SBOM document in `/var/lib/db/sbom` without the fields the syft schema requires

- `src/Dockerfile` pins the version of the syft schema
6 changes: 6 additions & 0 deletions .changes/sbom-document-that-listed-every-file.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
type: fixed
---
SBOM document that listed every file of a directory as an entry of its own, which made it very large for a package with many files such as time zone data

- A directory is one entry
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
*.md diff=markdown
CHANGELOG.md merge=union

/.changes export-ignore
/.github export-ignore
.editorconfig export-ignore
.gitattributes export-ignore
Expand Down
10 changes: 5 additions & 5 deletions .github/workflows/actions.lock
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@ workflows:
'.github/workflows/pull-request.yaml':
- 'actions/checkout@v7.0.1'
- 'actions/download-artifact@v8.0.2'
- 'docker/login-action@v4.7.0'
- 'docker/login-action@v4.8.0'
'.github/workflows/release.yaml':
- 'actions/checkout@v7.0.1'
- 'actions/download-artifact@v8.0.2'
- 'docker/login-action@v4.7.0'
- 'docker/login-action@v4.8.0'
'.github/workflows/tests.yaml':
- 'actions/checkout@v7.0.1'
- 'actions/download-artifact@v8.0.2'
Expand All @@ -49,9 +49,9 @@ dependencies:
commit: 'sha1-c3c9e263c25d99ce0380d002d59b67737d91b0dc'
owner_id: 5429470
repo_id: 241092383
'docker/login-action@v4.7.0':
ref: 'v4.7.0'
commit: 'sha1-81c6f93d4bc991e510b342a00b419b3bb4efc5c6'
'docker/login-action@v4.8.0':
ref: 'v4.8.0'
commit: 'sha1-8aa5831fb8d48d8f76594c7ddde61ae58019f550'
owner_id: 5429470
repo_id: 287743349
'docker/setup-buildx-action@v4.4.1':
Expand Down
15 changes: 12 additions & 3 deletions .github/workflows/clean-manifests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ jobs:
if: ${{ github.repository == 'peakimages/sqlite' }}
permissions:
contents: read
actions: write # disable this workflow after a failed run
environment:
name: cleanup
deployment: false
Expand All @@ -41,11 +42,19 @@ jobs:
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_CLEANUP_TOKEN }}
run: scripts/clean-manifests.sh --delete 200

- name: Upload the list of deleted manifests
- name: Upload the lists of manifests
if: ${{ always() }}
uses: actions/upload-artifact@v7.0.2
with:
name: deleted-manifests
path: scripts/deleted-manifests.tsv
name: manifests
path: |
scripts/manifests-to-delete.tsv
scripts/deleted-manifests.tsv
if-no-files-found: ignore
retention-days: 90

- name: Disable this workflow after a failure
if: ${{ failure() }}
env:
GH_TOKEN: ${{ github.token }}
run: gh workflow disable clean-manifests.yaml --repo "$GITHUB_REPOSITORY"
2 changes: 1 addition & 1 deletion .github/workflows/pull-request.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ jobs:
path: ${{ runner.temp }}/images

- name: Log in to ghcr.io
uses: docker/login-action@v4.7.0
uses: docker/login-action@v4.8.0
with:
registry: ghcr.io
username: ${{ github.actor }}
Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -88,14 +88,14 @@ jobs:
path: ${{ runner.temp }}/images

- name: Log in to ghcr.io
uses: docker/login-action@v4.7.0
uses: docker/login-action@v4.8.0
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Log in to docker.io
uses: docker/login-action@v4.7.0
uses: docker/login-action@v4.8.0
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
Expand Down Expand Up @@ -164,14 +164,14 @@ jobs:
echo "tags=$tags" | tee -a "$GITHUB_OUTPUT"

- name: Log in to ghcr.io
uses: docker/login-action@v4.7.0
uses: docker/login-action@v4.8.0
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Log in to docker.io
uses: docker/login-action@v4.7.0
uses: docker/login-action@v4.8.0
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,9 @@ jobs:
- name: Verify the action lockfile
env:
GH_TOKEN: ${{ github.token }}
# https://github.com/github/gh-actions-lock/releases/tag/v0.4.0
# https://github.com/github/gh-actions-lock/releases/tag/v0.4.1
run: |
gh extension install github/gh-actions-lock --pin v0.4.0
gh extension install github/gh-actions-lock --pin v0.4.1
gh actions-lock --verify

test:
Expand Down
34 changes: 30 additions & 4 deletions RELEASE.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
Every release is tagged `v<sqlite version>-v<image version>`, such as `v3.54.1-v1.4.0-beta.1`.

> [!IMPORTANT]
> A push that changes `release.json` or `src/upstream.json` publishes every release of it whose tag does not exist yet. Check what a push publishes with `scripts/plan-version.sh` first.
> A push that changes `release.json` or `src/upstream.json` releases the versions they set, unless they are released already. Run `scripts/plan-version.sh` first to see what a push releases.

## Rules

Expand All @@ -17,6 +17,7 @@ Every release is tagged `v<sqlite version>-v<image version>`, such as `v3.54.1-v
| `v1.2.x` | backports and hotfixes, cut from the newest tag of v1.2 | stable `1.2.x` versions |

- `main` takes fixes only. A new minor or major reaches it as a merge of `next` or a release branch
- Merge `next` or a release branch into `main` only right before you release it, on the same day. To freeze a minor for longer, publish an RC on `next` or cut `release/v1.4`
- `next` and the release branches publish prereleases only, their stable versions are released on `main`
- Name a branch after what it changes: `fix/<topic>`, `feat/<topic>`, and `fix/<topic>-v1.2.2` for a backport, after the version that releases it
- Squash merge pull requests from contributors and pull requests with a single change, such as SQLite updates
Expand All @@ -35,11 +36,12 @@ Every release is tagged `v<sqlite version>-v<image version>`, such as `v3.54.1-v
> [!NOTE]
> `next` publishes prereleases only. For its stable version, merge `next` into `main` with a merge commit and create the release on `main`.

1. Describe the changes under `## [Unreleased]` in `CHANGELOG.md`, they become the release notes
2. Check what the branch releases:
1. Describe the changes in change files with `scripts/change.sh`, they become the release notes, see [Change files](#change-files)
2. Check what the branch releases and the entries it lists:

```sh
scripts/plan-version.sh
scripts/change.sh --notes
```

3. Select the stage and the version, the script commits them and asks to push:
Expand All @@ -57,6 +59,27 @@ Every release is tagged `v<sqlite version>-v<image version>`, such as `v3.54.1-v
> scripts/undo-release.sh
> ```

## Change files

Every change to the image has a file in `.changes/`, `AGENTS.md` describes the types and the wording:

```text
---
type: fixed
commit: 1a2b3c4
---
Missing description and license on ghcr.io

- The release index carries the labels of the image as annotations
```

- `scripts/change.sh` writes a file, it asks for what its flags leave out, and `--notes` prints the entries of the next release
- `scripts/release.sh` preselects the version the files need and writes them into `CHANGELOG.md`, the release workflow reads its notes from there
- A prerelease writes the files under `## [Unreleased]`, keeps them and adds its version to the files that have none
- A stable release writes the files under `## [v1.4.0] - <date>` and removes them
- `scripts/undo-release.sh` brings the files back as they were
- A new SQLite version needs no file, the release workflow writes its notes

## Backport a fix

`main` is on 1.3, and 1.1 and 1.2 still get fixes. A fix reaches each of them through a patch branch and a pull request of its own:
Expand Down Expand Up @@ -130,8 +153,11 @@ Every release is tagged `v<sqlite version>-v<image version>`, such as `v3.54.1-v

## Pull requests

The pull request workflow builds every pull request and publishes the image of the line `latest` of one from a branch of this repository as `ghcr.io/peakimages/sqlite:pr-<number>`, versioned `<base>-pr.<number>`: pull request 1234 into `next` is `1.4.0-beta.1-pr.1234` after `1.4.0-beta.1` and `1.5.0-alpha.0-pr.1234` before the first alpha of 1.5.0, into `main` `1.3.4-pr.1234`. PHP's `version_compare` sorts it after its base and before the next prerelease or release.
The pull request workflow builds every pull request and publishes the image of the line `latest` of a pull request from a branch of this repository as `ghcr.io/peakimages/sqlite:pr-<number>`. Its version is `<base>-pr.<number>`:

- Into `next`: `1.4.0-beta.1-pr.1234` after `1.4.0-beta.1`, or `1.5.0-alpha.0-pr.1234` before the first alpha of 1.5.0
- Into `main`: `1.3.4-pr.1234`
- PHP's `version_compare` sorts it after its base and before the next prerelease or release
- Pull requests from forks and Dependabot are built, not published
- A push to a pull request from `main`, `next`, a release branch or a patch branch that changes `release.json` or `src/upstream.json` is not built, the release workflow builds it
- ghcr.io only, no git tag and no GitHub release, the clean manifests workflow keeps the image while the pull request is open
Expand Down
168 changes: 168 additions & 0 deletions scripts/change.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
#!/usr/bin/env bash
set -euo pipefail

CHANGE_DIRECTORY=.changes
TYPES='added,changed,deprecated,removed,fixed,security,maintenance'
SECTIONS='Breaking Changes,Security,Added,Changed,Deprecated,Removed,Fixed,Maintenance'

BOLD=$'\033[1m' GREEN=$'\033[32m' RED=$'\033[31m' DIM=$'\033[2m' RESET=$'\033[0m'

heading() { printf '\n %s%s%s\n' "$BOLD" "$*" "$RESET"; }
ok() { printf ' %s✔%s %s\n' "$GREEN" "$RESET" "$*"; }
die() {
printf ' %s✖%s %s\n' "$RED" "$RESET" "$*" >&2
exit 1
}
confirm() {
local answer
printf '\n %s [y/N] ' "$*"
read -r answer
[[ "$answer" =~ ^[Yy]$ ]]
}

cd "$(dirname "$0")/.."
trap 'tput cnorm 2> /dev/null || true' EXIT

main() {
read_arguments "$@"
if [ -n "$notes" ]; then
print_notes
return
fi
[ -n "$kind" ] && [ -n "$summary" ] || ask_questions
write_change
}

read_arguments() {
kind='' summary='' commit='' breaking='' notes='' details=()
while [ $# -gt 0 ]; do
case "$1" in
--notes) notes=true ;;
--breaking) breaking=true ;;
--type | --summary | --detail | --commit)
[ $# -ge 2 ] || die "$1 has no value, expected one"
case "$1" in
--type) kind="$2" ;;
--summary) summary="$2" ;;
--detail) details+=("$2") ;;
--commit) commit="$2" ;;
esac
shift
;;
*) die "'$1' is not an option, expected --type, --summary, --detail, --breaking, --commit or --notes" ;;
esac
shift
done
}

ask_questions() {
local selected detail types
[ -t 0 ] || die "--type and --summary are required without a terminal"
if [ -z "$kind" ]; then
heading "Select the type"
IFS=, read -ra types <<< "$TYPES"
selected="$(menu 0 "${types[@]}")"
kind="${types[$selected]}"
fi
if [ -z "$summary" ]; then
heading "Summary, one sentence with the heading"
printf ' '
read -r summary
fi
heading "Details, an empty line ends them"
while printf ' - ' && read -r detail && [ -n "$detail" ]; do
details+=("$detail")
done
if confirm "Breaking change?"; then
breaking=true
fi
printf '\n'
}

write_change() {
local path detail
[[ ",$TYPES," == *",$kind,"* ]] || die "the type is '$kind', expected ${TYPES//,/, }"
[ -n "$summary" ] || die "the summary is empty, expected one sentence"
if [ -n "$commit" ]; then
git rev-parse --verify --quiet "$commit^{commit}" > /dev/null || die "$commit is not a commit of this repository"
commit="$(git rev-parse --short "$commit")"
fi
mkdir -p "$CHANGE_DIRECTORY"
path="$CHANGE_DIRECTORY/$(file_name).md"
{
printf -- '---\ntype: %s\n' "$kind"
[ -z "$breaking" ] || printf 'breaking: true\n'
[ -z "$commit" ] || printf 'commit: %s\n' "$commit"
printf -- '---\n%s\n' "$summary"
[ "${#details[@]}" = 0 ] || printf '\n'
for detail in ${details[@]+"${details[@]}"}; do
printf -- '- %s\n' "$detail"
done
} > "$path"
ok "$path describes the $BOLD$kind$RESET change"
}

print_notes() {
local section entries sections
compgen -G "$CHANGE_DIRECTORY/*.md" > /dev/null || die "$CHANGE_DIRECTORY has no change files"
IFS=, read -ra sections <<< "$SECTIONS"
for section in "${sections[@]}"; do
entries="$(awk -v section="$section" '
FNR == 1 { fences = 0; type = ""; breaking = 0 }
fences < 2 {
if ($0 == "---") fences++
else if ($1 == "type:") type = $2
else if ($0 == "breaking: true") breaking = 1
next
}
NF && tolower(section) == (breaking ? "breaking changes" : type) {
prefix = /^- / ? " " : "- "
print prefix $0
}
' "$CHANGE_DIRECTORY"/*.md)"
[ -z "$entries" ] || printf '### %s\n\n%s\n\n' "$section" "$entries"
done
}

file_name() {
local stem name number=2
stem="$(printf '%s' "$summary" | LC_ALL=C tr -cs '[:alnum:]' '\n' | LC_ALL=C tr '[:upper:]' '[:lower:]' | awk 'NF && ++count <= 6' | paste -sd - -)"
name="$stem"
while [ -e "$CHANGE_DIRECTORY/$name.md" ]; do
name="$stem-$number"
number=$((number + 1))
done
echo "$name"
}

menu() {
local selected="$1" options key i
shift
options=("$@")
tput civis >&2 2> /dev/null || true
while true; do
for i in "${!options[@]}"; do
if [ "$i" = "$selected" ]; then
printf ' %s❯ %s%s\n' "$GREEN" "${options[$i]}" "$RESET" >&2
else
printf ' %s %s%s\n' "$DIM" "${options[$i]}" "$RESET" >&2
fi
done
IFS= read -rsn1 key
if [ "$key" = $'\x1b' ]; then
IFS= read -rsn2 key
if [ "$key" = '[A' ] && [ "$selected" -gt 0 ]; then
selected=$((selected - 1))
elif [ "$key" = '[B' ] && [ "$selected" -lt $((${#options[@]} - 1)) ]; then
selected=$((selected + 1))
fi
elif [ -z "$key" ]; then
break
fi
printf '\033[%dA\033[J' "${#options[@]}" >&2
done
tput cnorm >&2 2> /dev/null || true
echo "$selected"
}

main "$@"
Loading
Loading